@mybricks/plugin-ai 0.0.1 → 0.0.2
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/package.json +7 -2
- package/src/agents/app.ts +188 -60
- package/src/agents/common.ts +428 -68
- package/src/agents/custom.ts +14 -0
- package/src/agents/index.ts +31 -1
- package/src/agents/knowledges/README.md +614 -0
- package/src/agents/knowledges/SUMMARY.md +527 -0
- package/src/agents/knowledges/index.ts +8 -0
- package/src/agents/knowledges/knowledge-base.ts +565 -0
- package/src/agents/knowledges/knowledge-node.ts +266 -0
- package/src/agents/knowledges/types.ts +208 -0
- package/src/agents/utils/config.ts +427 -0
- package/src/agents/workspace/coding-manager.ts +31 -0
- package/src/agents/workspace/components-manager.ts +124 -0
- package/src/agents/workspace/outline-focus.ts +188 -0
- package/src/agents/workspace/outline-info.ts +520 -0
- package/src/agents/workspace/page-tree-generator.ts +83 -0
- package/src/agents/workspace/workspace.ts +319 -0
- package/src/agents/workspace-by-knowledges/MIGRATION.md +568 -0
- package/src/agents/workspace-by-knowledges/README.md +521 -0
- package/src/agents/workspace-by-knowledges/index.ts +11 -0
- package/src/agents/workspace-by-knowledges/providers/component-docs-provider.ts +92 -0
- package/src/agents/workspace-by-knowledges/providers/focus-info-provider.ts +131 -0
- package/src/agents/workspace-by-knowledges/providers/index.ts +8 -0
- package/src/agents/workspace-by-knowledges/providers/project-info-provider.ts +151 -0
- package/src/agents/workspace-by-knowledges/test.ts +240 -0
- package/src/agents/workspace-by-knowledges/types.ts +56 -0
- package/src/agents/workspace-by-knowledges/utils/components-manager.ts +145 -0
- package/src/agents/workspace-by-knowledges/utils/index.ts +8 -0
- package/src/agents/workspace-by-knowledges/utils/outline-focus.ts +178 -0
- package/src/agents/workspace-by-knowledges/utils/outline-info.ts +521 -0
- package/src/agents/workspace-by-knowledges/workspace.ts +166 -0
- package/src/api/cloud-components.ts +129 -0
- package/src/api-record-replay/README.md +187 -0
- package/src/api-record-replay/index.ts +11 -0
- package/src/api-record-replay/manager.ts +168 -0
- package/src/api-record-replay/recorder.ts +117 -0
- package/src/api-record-replay/replayer.ts +148 -0
- package/src/components/attachments/index.less +117 -0
- package/src/components/attachments/index.tsx +136 -0
- package/src/components/icons/index.tsx +21 -1
- package/src/components/index.less +34 -0
- package/src/components/mention/index.less +23 -0
- package/src/components/mention/index.tsx +19 -0
- package/src/components/messages/index.less +444 -237
- package/src/components/messages/index.tsx +371 -88
- package/src/components/sender/index.less +203 -0
- package/src/components/sender/index.tsx +298 -0
- package/src/components/types.ts +31 -0
- package/src/constants/index.ts +8 -0
- package/src/context/RequestStatusTracker.ts +50 -0
- package/src/context/index.ts +68 -6
- package/src/{types.d.ts → global.d.ts} +40 -5
- package/src/index.tsx +212 -32
- package/src/preset/agents.ts +380 -0
- package/src/preset/createTemplates.ts +25 -0
- package/src/preset/index.ts +12 -0
- package/src/preset/prompts.ts +235 -0
- package/src/preset/requestAsStream.ts +246 -0
- package/src/preset/user.ts +6 -0
- package/src/startView/components/header/header.less +17 -0
- package/src/startView/components/header/header.tsx +15 -0
- package/src/startView/components/index.ts +1 -0
- package/src/startView/index.less +22 -204
- package/src/startView/index.tsx +35 -203
- package/src/tools/analyze-and-expand-prd.ts +192 -86
- package/src/tools/analyze-requirement-and-components.ts +589 -0
- package/src/tools/answer.ts +59 -0
- package/src/tools/build-process.ts +1174 -0
- package/src/tools/coding-subagent-as-tool.ts +119 -0
- package/src/tools/generate-ui-content.ts +1083 -0
- package/src/tools/index.ts +22 -19
- package/src/tools/open-dsl.ts +69 -0
- package/src/tools/refactor-ui-content.ts +801 -0
- package/src/tools/utils.ts +880 -28
- package/src/types/index.ts +4 -0
- package/src/view/components/header/header.less +36 -2
- package/src/view/components/header/header.tsx +47 -2
- package/src/view/components/index.ts +0 -2
- package/src/view/index.tsx +158 -8
- package/src/tools/answer-user.ts +0 -35
- package/src/tools/focus-element.ts +0 -47
- package/src/tools/generate-page.ts +0 -750
- package/src/tools/get-component-info-by-ids.ts +0 -166
- package/src/tools/get-component-info.ts +0 -53
- package/src/tools/get-components-doc-and-prd.ts +0 -137
- package/src/tools/get-focus-mybricks-dsl.ts +0 -26
- package/src/tools/get-mybricks-dsl.ts +0 -73
- package/src/tools/modify-component.ts +0 -385
- package/src/view/components/messages/messages.less +0 -228
- package/src/view/components/messages/messages.tsx +0 -172
- package/src/view/components/sender/sender.less +0 -44
- package/src/view/components/sender/sender.tsx +0 -62
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { OutlineInfoManager, type OutlineNode, type SlotInfo } from './outline-info';
|
|
2
|
+
import { ComponentsManager } from './components-manager'
|
|
3
|
+
|
|
4
|
+
export interface FocusInfo {
|
|
5
|
+
pageId?: string;
|
|
6
|
+
comId?: string;
|
|
7
|
+
title?: string;
|
|
8
|
+
type?: 'page' | 'uiCom' | 'section' | 'logicCom';
|
|
9
|
+
focusArea?: {
|
|
10
|
+
selector: string;
|
|
11
|
+
title: string;
|
|
12
|
+
}
|
|
13
|
+
diagramId?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class PageHierarchyGenerator {
|
|
17
|
+
static generate(outlineInfo: OutlineNode, currentFocus: FocusInfo): string {
|
|
18
|
+
const processedData = currentFocus.type === 'uiCom'
|
|
19
|
+
? this.filterToFocusedComponent(outlineInfo, currentFocus.comId!)
|
|
20
|
+
: outlineInfo;
|
|
21
|
+
|
|
22
|
+
return this.generateTreeDescription(processedData as OutlineNode, {
|
|
23
|
+
pageId: currentFocus.pageId,
|
|
24
|
+
comId: currentFocus.comId,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private static containsComponent(data: OutlineNode, targetId: string): boolean {
|
|
29
|
+
if (!data) return false;
|
|
30
|
+
if (data.id === targetId) return true;
|
|
31
|
+
|
|
32
|
+
if (data.slots && Array.isArray(data.slots)) {
|
|
33
|
+
return data.slots.some(slot => {
|
|
34
|
+
if (slot.components && Array.isArray(slot.components)) {
|
|
35
|
+
return slot.components.some(component => this.containsComponent(component, targetId));
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private static hasChildren(data: OutlineNode): boolean {
|
|
44
|
+
if (!data?.slots || !Array.isArray(data.slots)) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return data.slots.some(slot => {
|
|
48
|
+
return slot.components && Array.isArray(slot.components) && slot.components.length > 0;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private static filterToFocusedComponent(data: OutlineNode, targetId: string): OutlineNode | null {
|
|
53
|
+
if (!data) return null;
|
|
54
|
+
|
|
55
|
+
if (data.id === targetId) {
|
|
56
|
+
return data;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (data.slots && Array.isArray(data.slots)) {
|
|
60
|
+
const filteredSlots = data.slots.map(slot => {
|
|
61
|
+
if (slot.components && Array.isArray(slot.components)) {
|
|
62
|
+
const filteredComponents = slot.components.map(component => {
|
|
63
|
+
if (this.containsComponent(component, targetId)) {
|
|
64
|
+
return this.filterToFocusedComponent(component, targetId);
|
|
65
|
+
} else {
|
|
66
|
+
const hasChildComponents = this.hasChildren(component);
|
|
67
|
+
return {
|
|
68
|
+
...component,
|
|
69
|
+
slots: undefined,
|
|
70
|
+
_hasCollapsedChildren: hasChildComponents
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}).filter(Boolean) as OutlineNode[];
|
|
74
|
+
|
|
75
|
+
return filteredComponents.length > 0 ? { ...slot, components: filteredComponents } : null;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}).filter(Boolean) as SlotInfo[];
|
|
79
|
+
|
|
80
|
+
if (filteredSlots.length > 0) {
|
|
81
|
+
return { ...data, slots: filteredSlots };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private static generateTreeDescription(data: OutlineNode | OutlineNode[], focusInfo: FocusInfo, level = 0): string {
|
|
88
|
+
const indent = ' '.repeat(level);
|
|
89
|
+
let result = '';
|
|
90
|
+
|
|
91
|
+
if (!data) {
|
|
92
|
+
return '无内容,代表内容为空';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (Array.isArray(data)) {
|
|
96
|
+
if (data.length === 0) {
|
|
97
|
+
return '无内容,代表内容为空';
|
|
98
|
+
}
|
|
99
|
+
data.forEach(item => {
|
|
100
|
+
result += this.generateTreeDescription(item, focusInfo, level);
|
|
101
|
+
});
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (data.asRoot) {
|
|
106
|
+
if (Array.isArray(data.slots?.[0]?.components)) {
|
|
107
|
+
data.slots?.[0]?.components.forEach(component => {
|
|
108
|
+
result += this.generateTreeDescription(component, focusInfo, level);
|
|
109
|
+
});
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (data.title) {
|
|
115
|
+
const namespace = data.def?.namespace;
|
|
116
|
+
const isFocused = data.id === focusInfo.comId ||
|
|
117
|
+
data.id === focusInfo.pageId;
|
|
118
|
+
const focusMarker = isFocused ? ' 【当前聚焦】' : '';
|
|
119
|
+
const collapsedMarker = data._hasCollapsedChildren ? ' 【子组件已折叠】' : '';
|
|
120
|
+
|
|
121
|
+
result += `${indent}- ${data.title}[id=${data.id}]${namespace ? `(${ComponentsManager.getAbbreviation(namespace)})` : ''}${focusMarker}${collapsedMarker}\n`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (data.slots && Array.isArray(data.slots)) {
|
|
125
|
+
data.slots.forEach(slot => {
|
|
126
|
+
if (slot.components && Array.isArray(slot.components)) {
|
|
127
|
+
slot.components.forEach(component => {
|
|
128
|
+
result += this.generateTreeDescription(component, focusInfo, level + 1);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
class FocusDescriptionGenerator {
|
|
139
|
+
static generate(currentFocus: FocusInfo): string {
|
|
140
|
+
const { pageId, comId, title, type } = currentFocus ?? {};
|
|
141
|
+
|
|
142
|
+
if (!currentFocus || (!currentFocus.pageId && !currentFocus.comId)) {
|
|
143
|
+
return '当前没有聚焦到任何页面或组件。';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let focusDesc = '';
|
|
147
|
+
|
|
148
|
+
switch (type) {
|
|
149
|
+
case 'uiCom':
|
|
150
|
+
focusDesc = `组件(title=${title},组件id=${comId})`;
|
|
151
|
+
break;
|
|
152
|
+
case 'page':
|
|
153
|
+
case 'section':
|
|
154
|
+
focusDesc = `页面(title=${title},页面id=${pageId})`;
|
|
155
|
+
break;
|
|
156
|
+
default:
|
|
157
|
+
focusDesc = `未知类型(title=${title})`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return `当前已聚焦到${focusDesc}中,后续用户的提问,关于"这个"、"此"、"整体",甚至不提主语,都是指代此元素及其子组件内容。`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export class FocusOutlineInfoManager extends OutlineInfoManager {
|
|
165
|
+
private focusInfo: FocusInfo;
|
|
166
|
+
private focusPageOutlineInfo: OutlineNode | null;
|
|
167
|
+
|
|
168
|
+
constructor({ api, focusInfo }: { api: any, focusInfo: FocusInfo }) {
|
|
169
|
+
super({ api });
|
|
170
|
+
this.focusInfo = { ...focusInfo };
|
|
171
|
+
this.focusPageOutlineInfo = focusInfo?.pageId ? this.getPageOutline(focusInfo.pageId) : null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
getFocusPageOutline(): OutlineNode | null {
|
|
175
|
+
return this.focusPageOutlineInfo;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
generateFocusHierarchy(): string {
|
|
179
|
+
if (!this.focusPageOutlineInfo || !this.focusInfo?.pageId) {
|
|
180
|
+
return '无内容,代表内容为空';
|
|
181
|
+
}
|
|
182
|
+
return PageHierarchyGenerator.generate(this.focusPageOutlineInfo, this.focusInfo);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
generateFocusDescription(): string {
|
|
186
|
+
return FocusDescriptionGenerator.generate(this.focusInfo);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
import { ComponentsManager } from "./components-manager";
|
|
2
|
+
|
|
3
|
+
export interface SlotInfo {
|
|
4
|
+
id: string;
|
|
5
|
+
title?: string;
|
|
6
|
+
layout?: any;
|
|
7
|
+
components?: OutlineNode[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface OutlineNode {
|
|
11
|
+
id: string;
|
|
12
|
+
title: string;
|
|
13
|
+
def?: {
|
|
14
|
+
namespace?: string;
|
|
15
|
+
};
|
|
16
|
+
asRoot?: boolean;
|
|
17
|
+
data?: any;
|
|
18
|
+
style?: any;
|
|
19
|
+
slots?: SlotInfo[];
|
|
20
|
+
_hasCollapsedChildren?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 设计器给出的 OutlineNode
|
|
24
|
+
export interface OriginOutlineNode extends OutlineNode {
|
|
25
|
+
components: OutlineNode[]
|
|
26
|
+
layout: {
|
|
27
|
+
width: string | number
|
|
28
|
+
height: string | number
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
export interface ComponentsResult {
|
|
34
|
+
id: string;
|
|
35
|
+
jsx: string;
|
|
36
|
+
namespaces: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ROOT_NAMESPACE = 'root';
|
|
40
|
+
const ROOT_ID = '_root_';
|
|
41
|
+
const ROOT_SLOT_ID = '_rootSlot_';
|
|
42
|
+
|
|
43
|
+
export class OutlineInfoManager {
|
|
44
|
+
|
|
45
|
+
api: any;
|
|
46
|
+
|
|
47
|
+
constructor({ api }: { api: any }) {
|
|
48
|
+
this.api = api;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private getOutlineInfo(id: string, type: string): OriginOutlineNode {
|
|
52
|
+
if (type === "logicCom") {
|
|
53
|
+
return this.api?.logicCom?.api?.getOutlineInfo(id)
|
|
54
|
+
} else if (type === "uiCom") {
|
|
55
|
+
return this.api?.uiCom?.api?.getOutlineInfo(id)
|
|
56
|
+
} else {
|
|
57
|
+
return this.api?.page?.api?.getOutlineInfo(id)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getPageOutline(pageId: string) {
|
|
62
|
+
return this.normalizePageOutline(this.getOutlineInfo(pageId, 'page'), pageId);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getUiComOutline(componentId: string) {
|
|
66
|
+
return this.getOutlineInfo(componentId, 'uiCom');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getLogicComOutline(componentId: string) {
|
|
70
|
+
return this.getOutlineInfo(componentId, 'logicCom');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private getOriginOutlineRootCom = (originOutline: OriginOutlineNode) => {
|
|
74
|
+
return originOutline?.components?.[0]?.asRoot ? originOutline?.components?.[0] : null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getPageMetaInfo(pageId: string) {
|
|
78
|
+
const originOutlineJson = this.getOutlineInfo(pageId, 'page')
|
|
79
|
+
|
|
80
|
+
const rootId = this.getOriginOutlineRootCom(originOutlineJson) ? this.getOriginOutlineRootCom(originOutlineJson)?.id : undefined;
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
pageId,
|
|
84
|
+
rootId
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
private normalizePageOutline(outline: OriginOutlineNode, pageId: string): OutlineNode {
|
|
90
|
+
if (outline?.id === pageId) {
|
|
91
|
+
|
|
92
|
+
let rootNode: OutlineNode = outline
|
|
93
|
+
let rootSlots: SlotInfo[] | undefined = [{
|
|
94
|
+
id: ROOT_SLOT_ID,
|
|
95
|
+
components: outline?.components,
|
|
96
|
+
layout: outline?.layout
|
|
97
|
+
}];
|
|
98
|
+
const whInfo: any = {
|
|
99
|
+
width: outline?.layout?.width,
|
|
100
|
+
height: outline?.layout?.height,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 说明有asRoot组件,把asRoot的信息往上提取一层
|
|
104
|
+
const rootCom = this.getOriginOutlineRootCom(outline)
|
|
105
|
+
if (!!rootCom) {
|
|
106
|
+
rootNode = rootCom;
|
|
107
|
+
rootSlots = rootNode.slots
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const normalized = {
|
|
111
|
+
...rootNode,
|
|
112
|
+
style: {
|
|
113
|
+
...(rootNode.style ?? {}),
|
|
114
|
+
...whInfo, // 注意用的是页面的宽高
|
|
115
|
+
},
|
|
116
|
+
slots: rootSlots,
|
|
117
|
+
def: {
|
|
118
|
+
version: '1.0.0',
|
|
119
|
+
namespace: ROOT_NAMESPACE
|
|
120
|
+
},
|
|
121
|
+
asRoot: true
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
id: pageId,
|
|
126
|
+
title: outline.title, // 注意用的是页面的title
|
|
127
|
+
slots: [{ id: ROOT_ID, components: [normalized] }]
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
id: pageId,
|
|
133
|
+
title: outline.title,
|
|
134
|
+
slots: [{ id: ROOT_ID, components: [outline] }]
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
getComponentIdToTitleMap(pageId: string) {
|
|
139
|
+
const outline = this.getPageOutline(pageId);
|
|
140
|
+
const componentMap = new Map<string, string>();
|
|
141
|
+
|
|
142
|
+
componentMap.set(ROOT_ID, outline.title ?? '页面根节点');
|
|
143
|
+
|
|
144
|
+
function traverse(data: any) {
|
|
145
|
+
if (!data) return;
|
|
146
|
+
|
|
147
|
+
if (Array.isArray(data)) {
|
|
148
|
+
data.forEach(item => traverse(item));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (data.id && data.title) {
|
|
153
|
+
componentMap.set(data.id, data.title);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (data.slots && Array.isArray(data.slots)) {
|
|
157
|
+
data.slots.forEach((slot: SlotInfo) => {
|
|
158
|
+
if (slot.components && Array.isArray(slot.components)) {
|
|
159
|
+
slot.components.forEach(component => traverse(component));
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
traverse(outline);
|
|
166
|
+
return componentMap;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
generateJSXByPageId(pageId: string, targetComponentIds: string[] = []): ComponentsResult {
|
|
170
|
+
const outline = this.getPageOutline(pageId);
|
|
171
|
+
return this.generateJSXByOutline(outline, targetComponentIds);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
generateJSXByOutline(outlineInfo: OutlineNode, targetComponentIds: string[] = []): ComponentsResult {
|
|
175
|
+
return OutlineJSXGenerator.generate(outlineInfo, targetComponentIds);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
findParentNodeByComId(pageOutlineInfo: OutlineNode, comId: string): OutlineNode | null {
|
|
179
|
+
function helper(node: OutlineNode): OutlineNode | null {
|
|
180
|
+
if (!node || !node.slots) return null;
|
|
181
|
+
for (const slot of node.slots || []) {
|
|
182
|
+
if (slot.components && Array.isArray(slot.components)) {
|
|
183
|
+
for (const component of slot.components) {
|
|
184
|
+
if (component.id === comId) {
|
|
185
|
+
return node;
|
|
186
|
+
}
|
|
187
|
+
// 向下递归
|
|
188
|
+
const found = helper(component);
|
|
189
|
+
if (found) return found;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
return helper(pageOutlineInfo);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
class OutlineJSXGenerator {
|
|
200
|
+
private static namespacesSet = new Set<string>();
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* 遮蔽base64和svg代码,避免DSL过大
|
|
204
|
+
*/
|
|
205
|
+
private static maskLargeContent(obj: any): any {
|
|
206
|
+
if (obj === null || obj === undefined) {
|
|
207
|
+
return obj;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 如果是字符串,检查是否为base64或svg
|
|
211
|
+
if (typeof obj === 'string') {
|
|
212
|
+
// 检测base64图片(data:image开头或长base64字符串)
|
|
213
|
+
if (obj.startsWith('data:image/') || /^[A-Za-z0-9+/=]{100,}$/.test(obj)) {
|
|
214
|
+
const prefix = obj.substring(0, 22);
|
|
215
|
+
return `[BASE64_MASKED:${prefix}...length:${obj.length}]`;
|
|
216
|
+
}
|
|
217
|
+
// 检测SVG代码
|
|
218
|
+
if (obj.includes('<svg') || obj.includes('<?xml') && obj.includes('svg')) {
|
|
219
|
+
const length = obj.length;
|
|
220
|
+
if (length > 200) {
|
|
221
|
+
return `[SVG_MASKED:length:${length}]`;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return obj;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 如果是数组,递归处理每个元素
|
|
228
|
+
if (Array.isArray(obj)) {
|
|
229
|
+
return obj.map(item => this.maskLargeContent(item));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 如果是对象,递归处理每个属性
|
|
233
|
+
if (typeof obj === 'object') {
|
|
234
|
+
const result: any = {};
|
|
235
|
+
for (const key in obj) {
|
|
236
|
+
if (obj.hasOwnProperty(key)) {
|
|
237
|
+
result[key] = this.maskLargeContent(obj[key]);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return obj;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
static generate(outlineInfo: OutlineNode, targetComponentIds: string[] = []): ComponentsResult {
|
|
247
|
+
this.namespacesSet.clear();
|
|
248
|
+
|
|
249
|
+
if (targetComponentIds.length === 0) {
|
|
250
|
+
const jsx = this.processData(outlineInfo);
|
|
251
|
+
return {
|
|
252
|
+
id: outlineInfo.id,
|
|
253
|
+
jsx,
|
|
254
|
+
namespaces: Array.from(this.namespacesSet)
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (targetComponentIds.length === 1) {
|
|
259
|
+
const targetNode = this.findNodeById(outlineInfo, targetComponentIds[0]);
|
|
260
|
+
if (targetNode) {
|
|
261
|
+
const jsx = this.processData(targetNode);
|
|
262
|
+
return {
|
|
263
|
+
id: targetNode.id,
|
|
264
|
+
jsx,
|
|
265
|
+
namespaces: Array.from(this.namespacesSet)
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const ancestorNodes = this.findMinimalCommonAncestors(outlineInfo, targetComponentIds);
|
|
271
|
+
const jsx = ancestorNodes.map(node => this.processData(node)).join('\n');
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
id: ancestorNodes[0]?.id,
|
|
275
|
+
jsx,
|
|
276
|
+
namespaces: Array.from(this.namespacesSet)
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private static findMinimalCommonAncestors(root: OutlineNode, targetIds: string[]): OutlineNode[] {
|
|
281
|
+
if (targetIds.length === 0) return [root];
|
|
282
|
+
if (targetIds.length === 1) {
|
|
283
|
+
const targetNode = this.findNodeById(root, targetIds[0]);
|
|
284
|
+
return targetNode ? [targetNode] : [];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const paths: OutlineNode[][] = [];
|
|
288
|
+
for (const targetId of targetIds) {
|
|
289
|
+
const path = this.findPathToNode(root, targetId);
|
|
290
|
+
if (path) {
|
|
291
|
+
paths.push(path);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (paths.length === 0) {
|
|
296
|
+
return [];
|
|
297
|
+
}
|
|
298
|
+
if (paths.length === 1) {
|
|
299
|
+
return [paths[0][paths[0].length - 1]];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
let commonAncestor: OutlineNode | null = null;
|
|
303
|
+
const minLength = Math.min(...paths.map(path => path.length));
|
|
304
|
+
|
|
305
|
+
for (let i = 0; i < minLength; i++) {
|
|
306
|
+
const currentNodes = paths.map(path => path[i]);
|
|
307
|
+
const firstNode = currentNodes[0];
|
|
308
|
+
|
|
309
|
+
if (currentNodes.every(node => node.id === firstNode.id)) {
|
|
310
|
+
commonAncestor = firstNode;
|
|
311
|
+
} else {
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (commonAncestor) {
|
|
317
|
+
return [commonAncestor];
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return [root];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private static findPathToNode(root: OutlineNode, targetId: string): OutlineNode[] | null {
|
|
324
|
+
if (root.id === targetId) {
|
|
325
|
+
return [root];
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (root.slots && Array.isArray(root.slots)) {
|
|
329
|
+
for (const slot of root.slots) {
|
|
330
|
+
if (slot.components && Array.isArray(slot.components)) {
|
|
331
|
+
for (const component of slot.components) {
|
|
332
|
+
const path = this.findPathToNode(component, targetId);
|
|
333
|
+
if (path) {
|
|
334
|
+
return [root, ...path];
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private static findNodeById(root: OutlineNode, targetId: string): OutlineNode | null {
|
|
345
|
+
if (root.id === targetId) {
|
|
346
|
+
return root;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (root.slots && Array.isArray(root.slots)) {
|
|
350
|
+
for (const slot of root.slots) {
|
|
351
|
+
if (slot.components && Array.isArray(slot.components)) {
|
|
352
|
+
for (const component of slot.components) {
|
|
353
|
+
const found = this.findNodeById(component, targetId);
|
|
354
|
+
if (found) return found;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private static extractLayout(style: any): Record<string, any> {
|
|
364
|
+
if (!style) return {};
|
|
365
|
+
|
|
366
|
+
const layout: Record<string, any> = {};
|
|
367
|
+
|
|
368
|
+
['width', 'height', 'margin', 'marginLeft', 'marginRight', 'marginTop', 'marginBottom']
|
|
369
|
+
.forEach(prop => {
|
|
370
|
+
if (style[prop] !== undefined) {
|
|
371
|
+
layout[prop] = style[prop];
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
if (style.layout !== undefined) {
|
|
376
|
+
if (style.layout === 'flex-column' || style.layout === 'flex') {
|
|
377
|
+
layout.display = 'flex';
|
|
378
|
+
layout.flexDirection = 'column';
|
|
379
|
+
}
|
|
380
|
+
if (style.layout === 'flex-row') {
|
|
381
|
+
layout.display = 'flex';
|
|
382
|
+
layout.flexDirection = 'row';
|
|
383
|
+
}
|
|
384
|
+
if (style.alignItems) layout.alignItems = style.alignItems;
|
|
385
|
+
if (style.justifyContent) layout.justifyContent = style.justifyContent;
|
|
386
|
+
if (style.layout === 'absolute') {
|
|
387
|
+
layout.position = 'relative';
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (style.position === 'absolute') {
|
|
392
|
+
['left', 'right', 'top', 'bottom', 'widthFact', 'heightFact', 'position']
|
|
393
|
+
.forEach(prop => {
|
|
394
|
+
if (style[prop] !== undefined) {
|
|
395
|
+
layout[prop] = style[prop];
|
|
396
|
+
}
|
|
397
|
+
})
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return layout;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
private static extractStyleArray(style: any): string[] {
|
|
404
|
+
if (!style?.css || !Array.isArray(style.css)) return [];
|
|
405
|
+
|
|
406
|
+
return style.css.map((cssItem: any) => {
|
|
407
|
+
const selector = cssItem.selector || '';
|
|
408
|
+
const cssProps = cssItem.css || {};
|
|
409
|
+
|
|
410
|
+
const cssString = Object.entries(cssProps)
|
|
411
|
+
.map(([key, value]) => `${key}: '${value}'`)
|
|
412
|
+
.join(', ');
|
|
413
|
+
|
|
414
|
+
return `${selector} : { ${cssString} }`;
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private static processData(node: OutlineNode | OutlineNode[]): string {
|
|
419
|
+
if (!node) return '';
|
|
420
|
+
|
|
421
|
+
if (Array.isArray(node)) {
|
|
422
|
+
return node.map(item => this.processData(item)).filter(Boolean).join('\n');
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (node.id && node.def?.namespace) {
|
|
426
|
+
return this.generateComponentJSX(node);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (node.slots && Array.isArray(node.slots)) {
|
|
430
|
+
return node.slots.map(slot => {
|
|
431
|
+
if (slot.components && Array.isArray(slot.components)) {
|
|
432
|
+
return this.processData(slot.components);
|
|
433
|
+
}
|
|
434
|
+
return '';
|
|
435
|
+
}).filter(Boolean).join('');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return '';
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
private static generateComponentJSX(node: OutlineNode, indent = ''): string {
|
|
442
|
+
if (!node?.id) return '';
|
|
443
|
+
|
|
444
|
+
const namespace = node.def?.namespace;
|
|
445
|
+
if (namespace !== ROOT_NAMESPACE) {
|
|
446
|
+
this.namespacesSet.add(namespace);
|
|
447
|
+
}
|
|
448
|
+
const layout = this.extractLayout(node.style);
|
|
449
|
+
const styleArray = this.extractStyleArray(node.style);
|
|
450
|
+
|
|
451
|
+
let jsx;
|
|
452
|
+
|
|
453
|
+
const namespaceTag = ComponentsManager.getAbbreviation(namespace)
|
|
454
|
+
|
|
455
|
+
// 遮蔽data中的base64和svg内容
|
|
456
|
+
const maskedData = node.data ? this.maskLargeContent(node.data) : null;
|
|
457
|
+
|
|
458
|
+
if (node.asRoot) {
|
|
459
|
+
jsx = `<${ROOT_NAMESPACE} id="${ROOT_ID}"` + (maskedData ? ` data={${JSON.stringify(maskedData)}}` : '');
|
|
460
|
+
} else {
|
|
461
|
+
jsx = `<${namespaceTag} id="${node.id}" title="${node.title}"` + (maskedData ? ` data={${JSON.stringify(maskedData)}}` : '');
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (Object.keys(layout).length > 0) {
|
|
465
|
+
jsx += ` layout={${JSON.stringify(layout)}}`;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (styleArray.length > 0) {
|
|
469
|
+
// 遮蔽styleArray中的base64和svg内容
|
|
470
|
+
const maskedStyleArray = styleArray.map(style => this.maskLargeContent(style));
|
|
471
|
+
jsx += ` styleAry={[${maskedStyleArray.map(style => `"${style}"`).join(', ')}]}`;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
jsx += ' >';
|
|
475
|
+
|
|
476
|
+
const slotsJSX = this.generateSlotsJSX(node.slots || [], indent + ' ');
|
|
477
|
+
if (slotsJSX) {
|
|
478
|
+
jsx += slotsJSX;
|
|
479
|
+
jsx += `\n${indent}</${namespaceTag}>`;
|
|
480
|
+
} else {
|
|
481
|
+
jsx += ' />';
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return jsx;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
private static generateSlotsJSX(slots: SlotInfo[], indent = ' '): string {
|
|
488
|
+
if (!slots || slots.length === 0) return '';
|
|
489
|
+
|
|
490
|
+
let slotsJSX = '';
|
|
491
|
+
slots.forEach(slot => {
|
|
492
|
+
if (slot.id) {
|
|
493
|
+
slotsJSX += `\n${indent}<slots.${slot.id}`;
|
|
494
|
+
|
|
495
|
+
if (slot.title) {
|
|
496
|
+
slotsJSX += ` title="${slot.title}"`;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (slot.layout) {
|
|
500
|
+
slotsJSX += ` layout={${JSON.stringify(this.extractLayout(slot.layout))}}`;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
slotsJSX += '>';
|
|
504
|
+
|
|
505
|
+
if (slot.components && Array.isArray(slot.components)) {
|
|
506
|
+
slot.components.forEach(component => {
|
|
507
|
+
const childJSX = this.generateComponentJSX(component, indent + ' ');
|
|
508
|
+
if (childJSX) {
|
|
509
|
+
slotsJSX += `\n${indent} ${childJSX}`;
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
slotsJSX += `\n${indent}</slots.${slot.id}>`;
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
return slotsJSX;
|
|
519
|
+
}
|
|
520
|
+
}
|