Skip to content

Commit 3e26600

Browse files
committed
feat: add image support to whiteboard-cli skill
- Add references/image.md with image processing workflow - Update content.md with strict image trigger condition - Update schema.md with Image node type definition - Update layout.md with image card layout rules - Add scenes/photo-showcase.md for image showcase layouts - Strict trigger: only when user explicitly requests images/配图/插图
1 parent 5a0e1d3 commit 3e26600

6 files changed

Lines changed: 281 additions & 0 deletions

File tree

skills/lark-whiteboard/references/content.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@
44

55
**用户 prompt 简短/模糊时**(如"画个漏斗图"、"画个架构图"),不要只输出字面内容。应适当补充该领域合理的内容
66

7+
## 图片需求识别
8+
9+
> **在规划内容之前,先判断是否需要插入真实图片。**
10+
11+
**触发条件(严格)**:仅当用户**显式说了**「图片、配图、插图、照片、真实图片、实拍」等词时,才使用 image 节点。
12+
13+
**不触发**:即使主题是旅行、美食、产品等视觉性内容,只要用户没显式要求图片,就不使用 image 节点,用文字 + 形状 + icon 呈现。
14+
15+
**识别到图片需求后**:参考 `references/image.md` 完成 Step 0(图片准备),再回来继续内容规划。
16+
17+
**图片数量规划**:3-6 张为宜。少于 3 张显得单薄,多于 6 张增加准备时间且布局拥挤。
18+
719
## 信息量参考
820

921
| 用户需求 | 合理的信息量 |
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# 图片资源处理
2+
3+
## 图片需求识别
4+
5+
**触发条件(严格)**:仅当用户**显式要求**使用图片时,才使用 image 节点。触发关键词:
6+
7+
> 图片、配图、插图、照片、真实图片、实拍、放一张图、加个图、嵌入图片
8+
9+
**不触发的情况**:即使主题涉及旅行、美食、产品、人物等视觉性内容,只要用户没有显式说要「图片/配图/插图」,就**一律不使用 image 节点**,用文字 + 形状 + icon 来呈现即可。
10+
11+
识别到图片需求后,先完成下方 Step 0,再进入 Workflow Step 1。
12+
13+
**图片数量**:3-6 张为宜。
14+
15+
## Step 0:图片准备
16+
17+
```
18+
0.1 识别图片需求(见上方触发关键词表)
19+
0.2 确定需要几张图,为每张图准备不同的搜索关键词(英文)
20+
0.3 逐张下载 → 校验每张图不同(md5) → 逐张上传到飞书 Drive
21+
0.4 收集所有 file_token,在 Step 2 生成 DSL 时引用
22+
```
23+
24+
## 上传步骤
25+
26+
**单张图片**
27+
```bash
28+
curl -L -o palace.jpg "https://example.com/palace.jpg"
29+
lark-cli drive +upload --file ./palace.jpg
30+
# 响应: { "file_token": "T8SBbLB5co85YLxuX8icHAlrnZg", ... }
31+
```
32+
33+
**多张图片(每张必须是不同的图)**
34+
```bash
35+
# 1. 每张图用不同的搜索词/URL 下载
36+
curl -L -o forbidden-city.jpg "https://unsplash.com/photos/<id1>/download?w=800"
37+
curl -L -o great-wall.jpg "https://unsplash.com/photos/<id2>/download?w=800"
38+
curl -L -o temple.jpg "https://unsplash.com/photos/<id3>/download?w=800"
39+
40+
# 2. 校验每张图确实不同
41+
md5 -r *.jpg # macOS,确认 hash 各不相同
42+
43+
# 3. 逐张上传,收集 token
44+
lark-cli drive +upload --file ./forbidden-city.jpg # → token_1
45+
lark-cli drive +upload --file ./great-wall.jpg # → token_2
46+
lark-cli drive +upload --file ./temple.jpg # → token_3
47+
```
48+
49+
> **多图常见错误**:用同一个 URL 参数下载多次,导致多张图片完全相同。每张图必须用不同的搜索关键词或不同的图片 ID。
50+
51+
## 图片来源策略
52+
53+
| 来源 | 方式 | 适用场景 |
54+
|------|------|----------|
55+
| 公开 URL | `curl -L -o file.jpg <URL>` 下载后上传 | 景点照片、开源图片 |
56+
| AI 生成 | 调用图片生成工具,保存后上传 | 插画、图标、概念图 |
57+
| 用户提供 | 用户给出本地路径或 URL | 产品截图、Logo |
58+
59+
> `image.src` 必须是飞书 Drive 的 `file_token`,不支持直接使用 URL。所有图片都需要先上传。

skills/lark-whiteboard/references/layout.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,3 +372,14 @@ npx -y @larksuite/whiteboard-cli@^0.2.0 -i skeleton.json -o step1.png -l coords.
372372
```
373373

374374
`alignItems: 'stretch'` + `width: 'fill-container'` = 等宽等高。
375+
376+
---
377+
378+
## 图文卡片
379+
380+
含图片的画板用图文卡片布局(vertical frame:图上文下):
381+
382+
- image 宽度 = 卡片宽度,height 按 3:2 比例(如 240×160)
383+
- 卡片间 gap: 24(比纯文字间距大)
384+
- 多卡片一行超过 3 张时,换行用嵌套 horizontal frame
385+
- 详见 `scenes/photo-showcase.md`

skills/lark-whiteboard/references/schema.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,81 @@ SVG 通过 `image/svg+xml` Blob 加载到画布,**不在 HTML DOM 中**,因
237237
"svg": { "code": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#3B82F6\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><polyline points=\"12 6 12 12 16 14\"/></svg>" } }
238238
```
239239

240+
### Image(图片节点)
241+
242+
在画板中嵌入图片。图片需先上传到飞书 Drive 获取 `file_token`
243+
244+
```typescript
245+
{
246+
type: 'image';
247+
id?: string;
248+
x?: number; y?: number;
249+
opacity?: number; // 0-1
250+
width: WBSizeValue;
251+
height: WBSizeValue;
252+
image: { src: string }; // 飞书 Drive file_token
253+
}
254+
```
255+
256+
#### 使用要求
257+
258+
图片必须先上传到飞书 Drive,`image.src` 填写返回的 `file_token`(如 `"T8SBbLB5co85YLxuX8icHAlrnZg"`)。
259+
260+
**上传步骤**
261+
```bash
262+
# 1. 上传图片到飞书 Drive
263+
lark-cli drive +upload --file ./beijing-palace.jpg
264+
265+
# 2. 从响应中提取 file_token,填入 DSL
266+
```
267+
268+
#### 尺寸建议
269+
270+
| 用途 | 推荐尺寸 | 说明 |
271+
|------|----------|------|
272+
| 卡片插图 | 200×150 ~ 300×200 | 配合文字卡片使用 |
273+
| 全宽背景 | 与 frame 同宽,高度按比例 | 用于地图、背景板 |
274+
| 缩略图/头像 | 60×60 ~ 100×100 | 列表项中的小图 |
275+
276+
> **宽高比**:建议保持原始图片的宽高比,避免拉伸变形。如果不确定原始比例,使用正方形(如 200×200)。
277+
278+
#### 典型用法
279+
280+
**1. 图文卡片**(图片 + 文字描述)
281+
282+
```json
283+
{
284+
"type": "frame", "layout": "vertical", "gap": 8, "padding": 0,
285+
"width": 240, "height": "fit-content",
286+
"fillColor": "#FFFFFF", "borderWidth": 1, "borderColor": "#E0E0E0", "borderRadius": 12,
287+
"children": [
288+
{ "type": "image", "width": 240, "height": 160,
289+
"image": { "src": "<file_token>" } },
290+
{ "type": "frame", "layout": "vertical", "gap": 4, "padding": [8, 12, 12, 12],
291+
"width": "fill-container", "height": "fit-content",
292+
"children": [
293+
{ "type": "text", "text": "故宫博物院", "fontSize": 14, "width": "fill-container", "height": "fit-content" },
294+
{ "type": "text", "text": "世界最大的古代宫殿建筑群", "fontSize": 11, "textColor": "#666666", "width": "fill-container", "height": "fit-content" }
295+
]
296+
}
297+
]
298+
}
299+
```
300+
301+
**2. 图片网格**(多张图片平铺)
302+
303+
```json
304+
{
305+
"type": "frame", "layout": "horizontal", "gap": 16, "padding": 16,
306+
"width": "fit-content", "height": "fit-content",
307+
"children": [
308+
{ "type": "image", "width": 200, "height": 150, "image": { "src": "<token_1>" } },
309+
{ "type": "image", "width": 200, "height": 150, "image": { "src": "<token_2>" } },
310+
{ "type": "image", "width": 200, "height": 150, "image": { "src": "<token_3>" } }
311+
]
312+
}
313+
```
314+
240315
### Icon(内置图标)
241316

242317
引用画板内置图标库的图标。比手写 SVG 更简单——只需指定 `name`

skills/lark-whiteboard/routes/dsl.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ Step 3: 渲染 & 审查 → 交付
7373
| 循环/飞轮图 | `scenes/flywheel.md` | 增长飞轮、闭环链路 |
7474
| 里程碑 | `scenes/milestone.md` | 时间线、版本演进 |
7575
| 流程图 | `scenes/flowchart.md` | 业务流、状态机、带条件判断的链路 |
76+
| 图片展示 | `scenes/photo-showcase.md` | 用户显式要求图片/配图/插图时 |
7677

7778
## 渲染前自查
7879

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# 图片展示 (Photo Showcase)
2+
3+
适用于:用户**显式要求使用图片/配图/插图**的场景(如"画一个带配图的旅行路线"、"做一个有图片的产品展示")。
4+
5+
> **注意**:仅当用户明确说了「图片/配图/插图/照片」等词时才进入本场景。单纯说"旅行路线图"、"产品展示"等不触发。
6+
7+
> **前置条件**:进入本场景前,必须已完成 SKILL.md 的"图片资源处理 → Step 0",拿到所有 file_token。
8+
9+
## Content 约束
10+
11+
- 图片 3-6 张,每张配标题(必需)+ 简短描述(可选,15字内)
12+
- **每张图必须是不同的真实图片**(不同 file_token),下载时用不同关键词/URL
13+
- 下载后用 `md5` 校验确保每张图不重复
14+
- 文字仅作辅助说明,图片是信息主体
15+
16+
## Layout 选型
17+
18+
| 模式 | 适用条件 | 特征 |
19+
|------|---------|------|
20+
| **卡片网格(默认)** | 多图平级展示(产品墙、团队介绍、美食推荐) | horizontal frame 内放等尺寸图文卡片 |
21+
| **路线时间线** | 有先后顺序(旅行路线、团建路线、项目演进) | 图文卡片 + connector 串联 |
22+
| **中心辐射** | 有一个核心主题 + 周围子项 | 中心标题 + 周围图文卡片 |
23+
24+
## Layout 规则
25+
26+
- **图文卡片结构**:vertical frame(图上文下),image 宽度 = 卡片宽度,height 按 3:2 比例
27+
- **卡片统一尺寸**:所有卡片宽高一致(推荐 240×280 或 200×250)
28+
- **图片统一尺寸**:所有 image 节点用相同 width/height(推荐 240×160 或 200×133)
29+
- **卡片间距**:gap: 24(比纯文字图表间距更大,让图片呼吸)
30+
- **卡片样式**:白色底 + 圆角 12 + 细边框,image 无圆角(紧贴卡片顶部)
31+
- **有序路线时**:卡片间用 connector 连接,connector 放顶层 nodes 数组
32+
33+
## 骨架示例
34+
35+
### 卡片网格(产品展示/团队介绍/美食推荐)
36+
37+
```json
38+
{
39+
"version": 2,
40+
"nodes": [
41+
{
42+
"type": "frame", "id": "grid", "layout": "vertical", "gap": 24, "padding": 32,
43+
"width": 840, "height": "fit-content",
44+
"children": [
45+
{ "type": "text", "id": "title", "width": 776, "height": 36,
46+
"text": "图表标题", "fontSize": 24, "textAlign": "center" },
47+
{
48+
"type": "frame", "id": "row", "layout": "horizontal", "gap": 24, "padding": 0,
49+
"width": "fit-content", "height": "fit-content",
50+
"children": [
51+
{
52+
"type": "frame", "id": "card-1", "layout": "vertical", "gap": 8, "padding": [0, 0, 12, 0],
53+
"width": 240, "height": "fit-content",
54+
"fillColor": "#FFFFFF", "borderWidth": 1, "borderColor": "#E0E0E0", "borderRadius": 12,
55+
"children": [
56+
{ "type": "image", "id": "img-1", "width": 240, "height": 160, "image": { "src": "<token_1>" } },
57+
{ "type": "text", "id": "t-1", "text": "标题", "fontSize": 14, "width": 216, "height": 20 },
58+
{ "type": "text", "id": "d-1", "text": "简短描述", "fontSize": 11, "color": "#666666", "width": 216, "height": 16 }
59+
]
60+
}
61+
]
62+
}
63+
]
64+
}
65+
]
66+
}
67+
```
68+
69+
每张图文卡片结构相同,复制并替换 `<token_N>`、标题和描述即可。3 张卡片一行,超过 3 张换行(嵌套第二个 horizontal frame)。
70+
71+
### 路线时间线(旅行路线/团建路线)
72+
73+
```json
74+
{
75+
"version": 2,
76+
"nodes": [
77+
{
78+
"type": "frame", "id": "route", "layout": "vertical", "gap": 24, "padding": 32,
79+
"width": 1100, "height": "fit-content",
80+
"children": [
81+
{ "type": "text", "id": "title", "width": 1036, "height": 36,
82+
"text": "路线标题", "fontSize": 24, "textAlign": "center" },
83+
{
84+
"type": "frame", "id": "stops", "layout": "horizontal", "gap": 32, "padding": 0,
85+
"width": "fit-content", "height": "fit-content",
86+
"children": [
87+
{
88+
"type": "frame", "id": "stop-1", "layout": "vertical", "gap": 8, "padding": [0, 0, 12, 0],
89+
"width": 240, "height": "fit-content",
90+
"fillColor": "#FFFFFF", "borderWidth": 1, "borderColor": "#E0E0E0", "borderRadius": 12,
91+
"children": [
92+
{ "type": "image", "id": "img-1", "width": 240, "height": 160, "image": { "src": "<token_1>" } },
93+
{ "type": "text", "id": "t-1", "text": "第1站:地点名", "fontSize": 14, "width": 216, "height": 20 }
94+
]
95+
},
96+
{
97+
"type": "frame", "id": "stop-2", "layout": "vertical", "gap": 8, "padding": [0, 0, 12, 0],
98+
"width": 240, "height": "fit-content",
99+
"fillColor": "#FFFFFF", "borderWidth": 1, "borderColor": "#E0E0E0", "borderRadius": 12,
100+
"children": [
101+
{ "type": "image", "id": "img-2", "width": 240, "height": 160, "image": { "src": "<token_2>" } },
102+
{ "type": "text", "id": "t-2", "text": "第2站:地点名", "fontSize": 14, "width": 216, "height": 20 }
103+
]
104+
}
105+
]
106+
}
107+
]
108+
},
109+
{ "type": "connector", "id": "c1", "connector": { "from": "stop-1", "to": "stop-2", "fromAnchor": "right", "toAnchor": "left" } }
110+
]
111+
}
112+
```
113+
114+
注意:connector 必须放在**顶层 nodes 数组**,不能嵌套在 frame.children 内。connector 的属性须包裹在 `connector` 字段中。
115+
116+
## 图片准备检查清单
117+
118+
生成 DSL 前确认:
119+
120+
- [ ] 所有 image 节点的 `image.src` 都是已上传的 file_token(非 URL)
121+
- [ ] 每个 file_token 不同(对应不同的真实图片)
122+
- [ ] 所有图片尺寸一致(同一画板内统一 width×height)
123+
- [ ] 图片宽高比合理(推荐 3:2,即 240×160)

0 commit comments

Comments
 (0)