@blocklet/component-studio-cli 0.5.45 → 0.5.48
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
CHANGED
|
@@ -42,6 +42,7 @@ alwaysApply: true
|
|
|
42
42
|
| `name` | 组件显示名称 |
|
|
43
43
|
| `description` | 组件描述 |
|
|
44
44
|
| `properties` | 包含所有可配置属性的对象 |
|
|
45
|
+
| `llmConfig` | AI 生成配置,用于描述 AI 如何处理各个属性字段,描述字段的配置用途 |
|
|
45
46
|
|
|
46
47
|
### 属性定义 (properties 内)
|
|
47
48
|
每个属性都有一个唯一 ID 作为键,包含以下内容:
|
|
@@ -97,6 +98,67 @@ alwaysApply: true
|
|
|
97
98
|
### 嵌套属性 (subProperties)
|
|
98
99
|
在 `yaml`, `json`, `array` 等复杂类型中可以定义子属性(subProperties)。子属性的结构与主属性列表结构相同,用于定义复杂数据类型的内部结构。
|
|
99
100
|
|
|
101
|
+
### LLM 配置 (llmConfig)
|
|
102
|
+
`llmConfig` 是必填的顶层字段,用于配置 AI 如何处理和生成组件属性的值。它主要用于 AI 生成内容的场景。
|
|
103
|
+
|
|
104
|
+
#### 基本结构
|
|
105
|
+
```json
|
|
106
|
+
"llmConfig": {
|
|
107
|
+
"properties": {
|
|
108
|
+
"propertyId": {
|
|
109
|
+
"key": "propertyKey",
|
|
110
|
+
"isNeedGenerate": true,
|
|
111
|
+
"describe": "详细描述 AI 如何处理这个属性的值,描述字段的含义",
|
|
112
|
+
"displayName": "属性显示名称"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### 字段说明
|
|
119
|
+
- `properties`: 包含需要 AI 处理的属性配置
|
|
120
|
+
- `key`: 对应 properties 中的属性 key,用于关联具体属性
|
|
121
|
+
- `isNeedGenerate`: 布尔值,标识该属性是否需要 AI 生成,大多数情况都是需要 AI 生成
|
|
122
|
+
- `describe`: 详细描述 AI 应该如何生成或处理这个属性的值,包括:
|
|
123
|
+
- 生成规则和约束
|
|
124
|
+
- 业务逻辑要求
|
|
125
|
+
- 数据格式要求
|
|
126
|
+
- TypeScript 接口定义
|
|
127
|
+
- `displayName`: 属性的显示名称,用于 AI 理解属性含义
|
|
128
|
+
|
|
129
|
+
#### 使用场景
|
|
130
|
+
- **内容生成**: 为文本、标题等内容属性提供生成指导
|
|
131
|
+
- **结构化数据**: 为复杂对象、数组等结构化数据提供格式规范
|
|
132
|
+
- **样式配置**: 为颜色、尺寸等样式属性提供选择指导
|
|
133
|
+
- **业务逻辑**: 为具有特定业务含义的属性提供生成规则
|
|
134
|
+
|
|
135
|
+
#### 最佳实践
|
|
136
|
+
- 在 `describe` 中提供详细的 TypeScript 接口定义
|
|
137
|
+
- 明确说明属性的业务含义和使用场景
|
|
138
|
+
- 为复杂类型提供完整的数据结构说明
|
|
139
|
+
- 设置合理的 `isNeedGenerate` 标识,避免不必要的 AI 处理
|
|
140
|
+
- 保持 `displayName` 与 properties 中的多语言名称一致
|
|
141
|
+
|
|
142
|
+
#### 示例
|
|
143
|
+
```json
|
|
144
|
+
"llmConfig": {
|
|
145
|
+
"properties": {
|
|
146
|
+
"gs1rn5jmxfvpxptx": {
|
|
147
|
+
"key": "title",
|
|
148
|
+
"isNeedGenerate": true,
|
|
149
|
+
"describe": "内容大标题,需要精炼",
|
|
150
|
+
"displayName": "标题"
|
|
151
|
+
},
|
|
152
|
+
"9ajrz12ik7esfk1z": {
|
|
153
|
+
"key": "description",
|
|
154
|
+
"isNeedGenerate": true,
|
|
155
|
+
"describe": "组件的描述,尽可能详细一些,用于解释这一块的内容",
|
|
156
|
+
"displayName": "描述"
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
100
162
|
|
|
101
163
|
## 常见问题
|
|
102
164
|
|
|
@@ -109,6 +171,18 @@ A: 可以通过修改浏览器语言设置或使用开发工具中的语言切
|
|
|
109
171
|
**Q: 修改属性类型后,原有的defaultValue不生效了?**
|
|
110
172
|
A: 不同类型需要匹配相应格式的默认值,修改类型时需同步更新defaultValue。
|
|
111
173
|
|
|
174
|
+
**Q: 为什么需要配置 llmConfig?**
|
|
175
|
+
A: 通过 llmConfig 可以指导 AI 如何正确生成各个属性的值,特别是字段代表的意思和用途。
|
|
176
|
+
|
|
177
|
+
**Q: llmConfig 中的 key 必须与 properties 中的 key 一致吗?**
|
|
178
|
+
A: 是的,llmConfig 中的 key 必须与 properties 中对应属性的 key 完全一致,这样 AI 才能正确关联和处理属性。
|
|
179
|
+
|
|
180
|
+
**Q: llmConfig 中的 describe 字段应该如何编写?**
|
|
181
|
+
A: describe 字段应该包含详细的类型定义、格式要求和业务规则,让 AI 能够准确理解字段的含义。
|
|
182
|
+
|
|
183
|
+
**Q: AI 是如何使用 llmConfig 的?**
|
|
184
|
+
A: AI 系统会读取 llmConfig 中的配置,根据 isNeedGenerate 标识决定是否生成对应属性,并根据 describe 中的描述和 zod 定义生成符合要求的数据结构。
|
|
185
|
+
|
|
112
186
|
## `@metadata.json` 示例
|
|
113
187
|
请仔细查看它的结构,其它 `@metadata.json` 的结构与其基本一致,只是其中 `properties` 中的差别
|
|
114
188
|
请注意:所有 `id` 都是 16 位的 uuid,不需要语意化,需要乱序字母和数字组合(非常重要!)
|
|
@@ -544,6 +618,126 @@ A: 不同类型需要匹配相应格式的默认值,修改类型时需同步
|
|
|
544
618
|
}
|
|
545
619
|
}
|
|
546
620
|
}
|
|
621
|
+
},
|
|
622
|
+
"llmConfig": {
|
|
623
|
+
"properties": {
|
|
624
|
+
"gs1rn5jmxfvpxptx": {
|
|
625
|
+
"key": "title",
|
|
626
|
+
"isNeedGenerate": true,
|
|
627
|
+
"describe": "内容大标题,需要精炼",
|
|
628
|
+
"displayName": "标题"
|
|
629
|
+
},
|
|
630
|
+
"9ajrz12ik7esfk1z": {
|
|
631
|
+
"key": "description",
|
|
632
|
+
"isNeedGenerate": true,
|
|
633
|
+
"describe": "组件的描述,尽可能详细一些,用于解释这一块的内容",
|
|
634
|
+
"displayName": "描述"
|
|
635
|
+
},
|
|
636
|
+
"3ckcfvf6b7zyskk8": {
|
|
637
|
+
"key": "logo",
|
|
638
|
+
"isNeedGenerate": false,
|
|
639
|
+
"describe": "Logo 图片配置,包含 URL 和尺寸信息",
|
|
640
|
+
"displayName": "Logo",
|
|
641
|
+
"subProperties": {
|
|
642
|
+
"ML-CDw7LvtlhM_cl": {
|
|
643
|
+
"key": "url",
|
|
644
|
+
"isNeedGenerate": false,
|
|
645
|
+
"describe": "Logo 图片的 URL 地址",
|
|
646
|
+
"displayName": "url"
|
|
647
|
+
},
|
|
648
|
+
"K-HYgPHtAsmO_mer": {
|
|
649
|
+
"key": "mediaKitUrl",
|
|
650
|
+
"isNeedGenerate": false,
|
|
651
|
+
"describe": "Logo 的媒体资源包 URL",
|
|
652
|
+
"displayName": "mediaKitUrl"
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
},
|
|
656
|
+
"x3lqht8ikble1itx": {
|
|
657
|
+
"key": "copyright",
|
|
658
|
+
"isNeedGenerate": false,
|
|
659
|
+
"describe": "版权信息文本",
|
|
660
|
+
"displayName": "版权信息"
|
|
661
|
+
},
|
|
662
|
+
"q0ezdj81v0m14y5m": {
|
|
663
|
+
"key": "number",
|
|
664
|
+
"isNeedGenerate": false,
|
|
665
|
+
"describe": "整数数值配置",
|
|
666
|
+
"displayName": "整数"
|
|
667
|
+
},
|
|
668
|
+
"yi1oj4rq1eziup1d": {
|
|
669
|
+
"key": "titleColor",
|
|
670
|
+
"isNeedGenerate": false,
|
|
671
|
+
"describe": "标题颜色,使用十六进制颜色值或 MUI 色板",
|
|
672
|
+
"displayName": "标题颜色"
|
|
673
|
+
},
|
|
674
|
+
"4f49q5uidkcp5ak4": {
|
|
675
|
+
"key": "json",
|
|
676
|
+
"isNeedGenerate": true,
|
|
677
|
+
"describe": "JSON 对象数据,包含 foo 和 bar 两个字段",
|
|
678
|
+
"displayName": "JSON 数据",
|
|
679
|
+
"subProperties": {
|
|
680
|
+
"gpy89bsxc6ovvlsp": {
|
|
681
|
+
"key": "foo",
|
|
682
|
+
"isNeedGenerate": true,
|
|
683
|
+
"describe": "JSON 对象中的 foo 字段值",
|
|
684
|
+
"displayName": "名称"
|
|
685
|
+
},
|
|
686
|
+
"1j34jdhdptp2xm5e": {
|
|
687
|
+
"key": "bar",
|
|
688
|
+
"isNeedGenerate": true,
|
|
689
|
+
"describe": "JSON 对象中的 bar 字段值",
|
|
690
|
+
"displayName": "属性"
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
},
|
|
694
|
+
"lbclpm6mxrp10w2k": {
|
|
695
|
+
"key": "array",
|
|
696
|
+
"isNeedGenerate": true,
|
|
697
|
+
"describe": "用户信息数组,每个元素包含 name 和 bio 字段",
|
|
698
|
+
"displayName": "数组数据",
|
|
699
|
+
"subProperties": {
|
|
700
|
+
"1c5vl2p9cn9ryvgh": {
|
|
701
|
+
"key": "name",
|
|
702
|
+
"isNeedGenerate": true,
|
|
703
|
+
"describe": "用户姓名",
|
|
704
|
+
"displayName": "姓名"
|
|
705
|
+
},
|
|
706
|
+
"c5whnccwzqqzaa0w": {
|
|
707
|
+
"key": "bio",
|
|
708
|
+
"isNeedGenerate": true,
|
|
709
|
+
"describe": "用户个人简介,支持多行文本",
|
|
710
|
+
"displayName": "简介"
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
},
|
|
714
|
+
"s0tund4p07bzizgv": {
|
|
715
|
+
"key": "yaml",
|
|
716
|
+
"isNeedGenerate": true,
|
|
717
|
+
"describe": "YAML 配置数据,包含 ya 和 ml 两个字段",
|
|
718
|
+
"displayName": "YAML 配置",
|
|
719
|
+
"subProperties": {
|
|
720
|
+
"1q8tsreh4k2mhbgs": {
|
|
721
|
+
"key": "ya",
|
|
722
|
+
"isNeedGenerate": true,
|
|
723
|
+
"describe": "YAML 配置中的 ya 字段值",
|
|
724
|
+
"displayName": "Ya"
|
|
725
|
+
},
|
|
726
|
+
"09w8sncxwrj6tldi": {
|
|
727
|
+
"key": "ml",
|
|
728
|
+
"isNeedGenerate": true,
|
|
729
|
+
"describe": "YAML 配置中的 ml 字段值",
|
|
730
|
+
"displayName": "Ml"
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
},
|
|
734
|
+
"8e7g6c61pxcy0q4w": {
|
|
735
|
+
"key": "children",
|
|
736
|
+
"isNeedGenerate": false,
|
|
737
|
+
"describe": "子组件配置,用于嵌套其他组件",
|
|
738
|
+
"displayName": "子组件"
|
|
739
|
+
}
|
|
740
|
+
}
|
|
547
741
|
}
|
|
548
742
|
}
|
|
549
743
|
```
|
|
@@ -83,46 +83,158 @@ export const EditComponent: React.FC<BlockProps & { onChange?: (value: BlockProp
|
|
|
83
83
|
- 考虑添加表单验证,确保用户输入的值有效
|
|
84
84
|
- 提供即时反馈,让用户知道其操作已被接收
|
|
85
85
|
- 保持编辑控件的样式与 Component Studio 整体风格一致
|
|
86
|
-
- 并不是所有的 Component
|
|
86
|
+
- 并不是所有的 Component 都需要这个自定义 EditComponent,尤其是,尤其是 `type: 'url' | ‘yml’` 情况下,不建议替换
|
|
87
87
|
|
|
88
|
-
###
|
|
89
|
-
|
|
88
|
+
### MUI 5.x 最佳实践
|
|
89
|
+
Component Studio 中推荐使用 Material-UI 5.x 组件系统,提供一致性和可访问性。便于快速使用定制的主题系统。
|
|
90
90
|
|
|
91
|
+
#### 优先使用 MUI 组件而非原生 HTML
|
|
91
92
|
```typescript
|
|
92
|
-
//
|
|
93
|
+
// ❌ 避免使用原生 HTML 组件
|
|
94
|
+
return (
|
|
95
|
+
<div>
|
|
96
|
+
<h1>Title</h1>
|
|
97
|
+
<p>Description</p>
|
|
98
|
+
<button onClick={handleClick}>Click me</button>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// ✅ 推荐使用 MUI 组件
|
|
103
|
+
import { Box, Typography, Button } from '@mui/material';
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<Box>
|
|
107
|
+
<Typography variant="h4" component="h1">
|
|
108
|
+
{title}
|
|
109
|
+
</Typography>
|
|
110
|
+
<Typography variant="body1">
|
|
111
|
+
{description}
|
|
112
|
+
</Typography>
|
|
113
|
+
<Button variant="contained" onClick={handleClick}>
|
|
114
|
+
Click me
|
|
115
|
+
</Button>
|
|
116
|
+
</Box>
|
|
117
|
+
);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### MUI 组件选择指南
|
|
121
|
+
- **布局容器**: 使用 `Box`, `Container`, `Stack`, `Grid` 替代 `div`
|
|
122
|
+
- **文本显示**: 使用 `Typography` 替代 `h1-h6`, `p`, `span`
|
|
123
|
+
- **交互元素**: 使用 `Button`, `IconButton` 替代 `button`
|
|
124
|
+
- **输入控件**: 使用 `TextField`, `Select`, `Checkbox` 等替代原生表单元素
|
|
125
|
+
- **卡片布局**: 使用 `Card`, `CardContent`, `CardActions` 组织内容
|
|
126
|
+
- **导航元素**: 使用 `AppBar`, `Tabs`, `Breadcrumbs` 等
|
|
127
|
+
|
|
128
|
+
### 样式最佳实践
|
|
129
|
+
Component Studio 中样式管理推荐使用 MUI 的 sx 属性和主题系统。
|
|
130
|
+
|
|
131
|
+
#### sx 属性优先于内联样式
|
|
132
|
+
```typescript
|
|
133
|
+
import { Box, Typography } from '@mui/material';
|
|
134
|
+
|
|
135
|
+
// ❌ 避免使用内联样式
|
|
93
136
|
const cardStyle = {
|
|
94
137
|
padding: '1.5rem',
|
|
95
138
|
borderRadius: '12px',
|
|
96
139
|
background: 'rgba(255, 255, 255, 0.7)',
|
|
97
140
|
boxShadow: '0 10px 30px rgba(0, 0, 0, 0.08)'
|
|
98
141
|
};
|
|
99
|
-
|
|
100
142
|
return <div style={cardStyle}>...</div>;
|
|
143
|
+
|
|
144
|
+
// ✅ 推荐使用 sx 属性
|
|
145
|
+
return (
|
|
146
|
+
<Box
|
|
147
|
+
sx={{
|
|
148
|
+
p: 3, // padding: theme.spacing(3)
|
|
149
|
+
borderRadius: 2, // borderRadius: theme.shape.borderRadius * 2
|
|
150
|
+
bgcolor: 'background.paper',
|
|
151
|
+
boxShadow: 3, // theme.shadows[3]
|
|
152
|
+
'&:hover': {
|
|
153
|
+
boxShadow: 6,
|
|
154
|
+
}
|
|
155
|
+
}}
|
|
156
|
+
>
|
|
157
|
+
...
|
|
158
|
+
</Box>
|
|
159
|
+
);
|
|
101
160
|
```
|
|
102
161
|
|
|
103
|
-
####
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
162
|
+
#### 使用主题系统避免硬编码
|
|
163
|
+
```typescript
|
|
164
|
+
// ❌ 避免硬编码颜色和尺寸
|
|
165
|
+
sx={{
|
|
166
|
+
color: '#1976d2',
|
|
167
|
+
fontSize: '24px',
|
|
168
|
+
margin: '16px'
|
|
169
|
+
}}
|
|
170
|
+
|
|
171
|
+
// ✅ 使用主题令牌
|
|
172
|
+
sx={{
|
|
173
|
+
color: 'primary.main', // 主题主色
|
|
174
|
+
fontSize: 'h4.fontSize', // 主题字体尺寸
|
|
175
|
+
m: 2, // theme.spacing(2)
|
|
176
|
+
bgcolor: 'background.paper', // 主题背景色
|
|
177
|
+
borderColor: 'divider' // 主题分割线颜色
|
|
178
|
+
}}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
#### 响应式设计最佳实践
|
|
182
|
+
```typescript
|
|
183
|
+
// 使用主题断点实现响应式
|
|
184
|
+
sx={{
|
|
185
|
+
p: {
|
|
186
|
+
xs: 1,
|
|
187
|
+
sm: 2,
|
|
188
|
+
md: 3
|
|
189
|
+
}
|
|
190
|
+
}}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
#### 动态样式与属性绑定
|
|
194
|
+
```typescript
|
|
195
|
+
// 根据属性动态应用样式
|
|
196
|
+
const getTitleSx = (titleColor?: string) => ({
|
|
197
|
+
color: titleColor || 'text.primary',
|
|
198
|
+
fontWeight: 'bold',
|
|
199
|
+
mb: 2
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<Typography
|
|
204
|
+
variant="h4"
|
|
205
|
+
sx={getTitleSx(titleColor)}
|
|
206
|
+
>
|
|
207
|
+
{title}
|
|
208
|
+
</Typography>
|
|
209
|
+
);
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
#### 主题自定义最佳实践
|
|
213
|
+
```typescript
|
|
214
|
+
// 在组件中访问主题
|
|
215
|
+
import { useTheme } from '@mui/material/styles';
|
|
216
|
+
|
|
217
|
+
const theme = useTheme();
|
|
218
|
+
|
|
219
|
+
// 使用主题值进行条件渲染
|
|
220
|
+
sx={{
|
|
221
|
+
color: theme.palette.mode === 'dark' ? 'grey.300' : 'grey.700',
|
|
222
|
+
bgcolor: theme.palette.background.paper,
|
|
223
|
+
border: `1px solid ${theme.palette.divider}`
|
|
224
|
+
}}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
#### 性能优化建议
|
|
228
|
+
- **缓存 sx 对象** - 使用 useMemo 缓存复杂的 sx 配置
|
|
118
229
|
```typescript
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
230
|
+
const cardSx = useMemo(() => ({
|
|
231
|
+
p: 3,
|
|
232
|
+
borderRadius: 2,
|
|
233
|
+
bgcolor: isActive ? 'primary.light' : 'background.paper'
|
|
234
|
+
}), [isActive]);
|
|
122
235
|
```
|
|
123
|
-
-
|
|
124
|
-
-
|
|
125
|
-
- **使用语义化命名** - 如 containerStyle, headerStyle 等增强可读性
|
|
236
|
+
- **避免复杂嵌套** - 将复杂样式拆分为多个组件
|
|
237
|
+
- **使用主题缓存** - 相同的主题令牌会被 MUI 自动缓存
|
|
126
238
|
|
|
127
239
|
## 与 @metadata.json 的关系
|
|
128
240
|
|
|
@@ -169,18 +281,30 @@ json?: {
|
|
|
169
281
|
对于需要特殊编辑体验的属性,可以通过 `EditComponent` 提供自定义编辑界面:
|
|
170
282
|
|
|
171
283
|
```typescript
|
|
284
|
+
import { Box, TextField, FormLabel } from '@mui/material';
|
|
285
|
+
|
|
172
286
|
export const EditComponent: React.FC<BlockProps & { onChange?: (value: BlockProps) => void }> = ({
|
|
173
287
|
onChange,
|
|
174
288
|
...props
|
|
175
289
|
}) => {
|
|
176
290
|
return (
|
|
177
|
-
<
|
|
178
|
-
<
|
|
179
|
-
|
|
180
|
-
|
|
291
|
+
<Box sx={{ p: 2 }}>
|
|
292
|
+
<FormLabel sx={{ mb: 1, display: 'block' }}>
|
|
293
|
+
Custom Editor
|
|
294
|
+
</FormLabel>
|
|
295
|
+
<TextField
|
|
296
|
+
fullWidth
|
|
297
|
+
variant="outlined"
|
|
298
|
+
size="small"
|
|
299
|
+
value={props.someProperty || ''}
|
|
181
300
|
onChange={(e) => onChange?.({...props, someProperty: e.target.value})}
|
|
301
|
+
sx={{
|
|
302
|
+
'& .MuiOutlinedInput-root': {
|
|
303
|
+
bgcolor: 'background.paper'
|
|
304
|
+
}
|
|
305
|
+
}}
|
|
182
306
|
/>
|
|
183
|
-
</
|
|
307
|
+
</Box>
|
|
184
308
|
);
|
|
185
309
|
}
|
|
186
310
|
```
|