@blocklet/component-studio-cli 0.5.47 → 0.5.49

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/component-studio-cli",
3
- "version": "0.5.47",
3
+ "version": "0.5.49",
4
4
  "description": "CLI for Component Studio",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -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 都需要这个自定义空间,尤其是 `type: 'url' | ‘yml’` 情况下,不建议替换
86
+ - 并不是所有的 Component 都需要这个自定义 EditComponent,尤其是,尤其是 `type: 'url' | ‘yml’` 情况下,不建议替换
87
87
 
88
- ### 样式
89
- 组件样式在 Component Studio 中通常通过内联样式对象实现,便于与属性值动态绑定。
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
- - **内联样式优先** - 使用内联样式(style对象)便于属性动态控制
105
- - **样式对象分离** - 对于复杂组件,将样式对象提取到组件函数外,提高可读性
106
- - **动态样式函数** - 创建函数生成依赖于属性的样式
107
- ```typescript
108
- const getTitleStyle = (color) => ({
109
- color,
110
- fontSize: '1.5rem',
111
- fontWeight: 600
112
- });
113
- ```
114
- - **响应式设计** - 使用条件样式或媒体查询实现适配不同设备
115
- - **过渡动画** - 添加 transition 属性提升用户体验
116
- - **交互增强** - 为互动元素添加悬停状态
117
- - **性能优化** - 使用 useMemo 缓存样式对象,避免不必要的重新计算
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 buttonStyle = useMemo(() => ({
120
- background: isActive ? activeColor : inactiveColor
121
- }), [isActive, activeColor, inactiveColor]);
230
+ const cardSx = useMemo(() => ({
231
+ p: 3,
232
+ borderRadius: 2,
233
+ bgcolor: isActive ? 'primary.light' : 'background.paper'
234
+ }), [isActive]);
122
235
  ```
123
- - **避免硬编码** - 使用属性传入的样式值,避免硬编码颜色等固定值
124
- - **主题变量** - 考虑使用 CSS 变量实现主题定制
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
- <div>
178
- <label>自定义编辑器</label>
179
- <input
180
- value={props.someProperty || ''}
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
- </div>
307
+ </Box>
184
308
  );
185
309
  }
186
310
  ```