@lemonppt/agent-prompts 0.1.7 → 0.2.1

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/patch.js ADDED
@@ -0,0 +1,677 @@
1
+ // lemonPPT - AI-powered presentation generation
2
+ // Copyright (c) 2026 lemonforme
3
+ // SPDX-License-Identifier: AGPL-3.0-or-later
4
+ // 使用内联 SVG 占位图,避免外部网络依赖导致渲染/导出超时或失败
5
+ const PLACEHOLDER_IMAGE = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJnIiB4MT0iMCUiIHkxPSIwJSIgeDI9IjEwMCUiIHkyPSIxMDAlIj48c3RvcCBvZmZzZXQ9IjAlIiBzdHlsZT0ic3RvcC1jb2xvcjojZTJlOGYwIi8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdHlsZT0ic3RvcC1jb2xvcjojY2JkNWUxIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHJlY3Qgd2lkdGg9IjEyMDAiIGhlaWdodD0iODAwIiBmaWxsPSJ1cmwoI2cpIi8+PGNpcmNsZSBjeD0iNjAwIiBjeT0iMzIwIiByPSIxMjAiIGZpbGw9IiM5NGEzYjgiIG9wYWNpdHk9IjAuMyIvPjxyZWN0IHg9IjQyMCIgeT0iNTIwIiB3aWR0aD0iMzYwIiBoZWlnaHQ9IjQwIiByeD0iOCIgZmlsbD0iIzk0YTNiOCIgb3BhY2l0eT0iMC4zIi8+PC9zdmc+';
6
+ const PLACEHOLDER_AVATAR = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImciIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0eWxlPSJzdG9wLWNvbG9yOiNjYmQ1ZTEiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0eWxlPSJzdG9wLWNvbG9yOiM5NGEzYjgiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0idXJsKCNnKSIvPjxjaXJjbGUgY3g9IjEwMCIgY3k9IjgwIiByPSI0MCIgZmlsbD0iIzY0NzQ4YiIvPjxlbGxpcHNlIGN4PSIxMDAiIGN5PSIxOTAiIHJ4PSI3MCIgcnk9IjUwIiBmaWxsPSIjNjQ3NDhiIi8+PC9zdmc+';
7
+ const PLACEHOLDER_COVER = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxOTIwIiBoZWlnaHQ9IjEwODAiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iZyIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3R5bGU9InN0b3AtY29sb3I6I2YxZjVmOSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3R5bGU9InN0b3AtY29sb3I6I2NiZDVlMSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHdpZHRoPSIxOTIwIiBoZWlnaHQ9IjEwODAiIGZpbGw9InVybCgjZykiLz48Y2lyY2xlIGN4PSI5NjAiIGN5PSI0NDAiIHI9IjIwMCIgZmlsbD0iIzk0YTNiOCIgb3BhY2l0eT0iMC4yNSIvPjxyZWN0IHg9IjY2MCIgeT0iNzYwIiB3aWR0aD0iNjAwIiBoZWlnaHQ9IjYwIiByeD0iMTIiIGZpbGw9IiM5NGEzYjgiIG9wYWNpdHk9IjAuMjUiLz48L3N2Zz4=';
8
+ function isNonEmptyString(value) {
9
+ return typeof value === 'string' && value.trim().length > 0;
10
+ }
11
+ function ensureString(props, key, fallback) {
12
+ if (isNonEmptyString(props[key]))
13
+ return props[key];
14
+ props[key] = fallback;
15
+ return fallback;
16
+ }
17
+ function ensureArray(props, key, fallback) {
18
+ const value = props[key];
19
+ if (Array.isArray(value) && value.length > 0)
20
+ return value;
21
+ props[key] = fallback;
22
+ return fallback;
23
+ }
24
+ function ensureObjectArray(props, key, fallback) {
25
+ const arr = ensureArray(props, key, fallback);
26
+ return arr.filter((item) => item !== null && typeof item === 'object');
27
+ }
28
+ function asStringArray(value) {
29
+ if (!Array.isArray(value))
30
+ return [];
31
+ return value.map((item) => (typeof item === 'string' ? item : String(item ?? ''))).filter(Boolean);
32
+ }
33
+ function asObjectArray(value) {
34
+ if (!Array.isArray(value))
35
+ return [];
36
+ return value.filter((item) => item !== null && typeof item === 'object');
37
+ }
38
+ function splitPoints(points, perSide = 2) {
39
+ const safe = points.filter(Boolean);
40
+ const mid = Math.min(perSide, Math.ceil(safe.length / 2));
41
+ return { left: safe.slice(0, mid), right: safe.slice(mid, mid + perSide) };
42
+ }
43
+ function objectToStringStep(step) {
44
+ const title = isNonEmptyString(step.title) ? step.title : '';
45
+ const description = isNonEmptyString(step.description) ? step.description : '';
46
+ return title && description ? `${title}:${description}` : title || description || '步骤';
47
+ }
48
+ function stringToObjectStep(step) {
49
+ const text = typeof step === 'string' ? step : String(step ?? '');
50
+ const [title = '步骤', description = ''] = text.split(/[::]/, 2).map((s) => s.trim());
51
+ return { title: title || '步骤', description };
52
+ }
53
+ function alignLabelsAndData(labels, data) {
54
+ const count = Math.max(1, Math.min(labels.length || data.length, 8));
55
+ const alignedLabels = [];
56
+ const alignedData = [];
57
+ for (let i = 0; i < count; i++) {
58
+ alignedLabels.push(labels[i] ?? `节点 ${i + 1}`);
59
+ alignedData.push(typeof data[i] === 'number' ? data[i] : 0);
60
+ }
61
+ return { labels: alignedLabels, data: alignedData };
62
+ }
63
+ function ensureTitle(props, fallback = '标题') {
64
+ ensureString(props, 'title', fallback);
65
+ }
66
+ function deriveValueFromMetrics(metrics) {
67
+ const first = metrics[0];
68
+ if (!first)
69
+ return '100';
70
+ if (isNonEmptyString(first.value))
71
+ return first.value;
72
+ return '100';
73
+ }
74
+ /**
75
+ * 根据具体版式补全/转换 props,保证每个版式都能拿到它真正需要的数据结构。
76
+ */
77
+ /**
78
+ * 将带主题前缀的 layout ID(如 theme01_process_v1)还原为版式 ID(process_v1),
79
+ * 同时兼容不带前缀的 legacy ID。
80
+ */
81
+ function normalizeLayoutId(layout) {
82
+ const idx = layout.indexOf('_');
83
+ return idx > 0 ? layout.slice(idx + 1) : layout;
84
+ }
85
+ function patchSlideContentForLayout(slide, props) {
86
+ const rawLayout = slide.layout ?? '';
87
+ if (!rawLayout)
88
+ return;
89
+ const layout = normalizeLayoutId(rawLayout);
90
+ // cover / closing / simple pages
91
+ if (layout.startsWith('cover')) {
92
+ ensureString(props, 'title', '演示标题');
93
+ return;
94
+ }
95
+ if (layout.startsWith('closing')) {
96
+ ensureString(props, 'title', '感谢观看');
97
+ return;
98
+ }
99
+ if (layout.startsWith('table_of_contents')) {
100
+ ensureTitle(props, '目录');
101
+ ensureArray(props, 'items', ['内容一', '内容二', '内容三']);
102
+ return;
103
+ }
104
+ // metric
105
+ if (layout === 'metric_v1' || layout === 'metric_v2') {
106
+ if (Array.isArray(props.metrics) && props.metrics.length > 0) {
107
+ const metrics = asObjectArray(props.metrics);
108
+ props.value = deriveValueFromMetrics(metrics);
109
+ if (!isNonEmptyString(props.unit) && isNonEmptyString(metrics[0]?.unit)) {
110
+ props.unit = metrics[0].unit;
111
+ }
112
+ if (!isNonEmptyString(props.label) && isNonEmptyString(metrics[0]?.label)) {
113
+ props.label = metrics[0].label;
114
+ }
115
+ }
116
+ ensureString(props, 'value', '100');
117
+ ensureString(props, 'unit', '%');
118
+ ensureString(props, 'label', '核心指标');
119
+ return;
120
+ }
121
+ if (layout === 'metric_v3') {
122
+ ensureTitle(props, '核心指标');
123
+ const metrics = ensureObjectArray(props, 'metrics', [
124
+ { label: '核心指标 A', value: '100', unit: '%', change: '+12%' },
125
+ { label: '核心指标 B', value: '85', unit: '%', change: '+8%' },
126
+ ]);
127
+ // 补齐每个指标字段
128
+ props.metrics = metrics.map((m) => ({
129
+ label: isNonEmptyString(m.label) ? m.label : '指标',
130
+ value: isNonEmptyString(m.value) ? m.value : '0',
131
+ unit: isNonEmptyString(m.unit) ? m.unit : '%',
132
+ change: isNonEmptyString(m.change) ? m.change : '',
133
+ }));
134
+ return;
135
+ }
136
+ // stats
137
+ if (layout.startsWith('stats')) {
138
+ ensureTitle(props, '关键指标');
139
+ const stats = ensureObjectArray(props, 'stats', [
140
+ { label: '指标 A', value: '100', unit: '%', change: '+10%' },
141
+ { label: '指标 B', value: '85', unit: '%', change: '+8%' },
142
+ { label: '指标 C', value: '70', unit: '%', change: '+5%' },
143
+ { label: '指标 D', value: '60', unit: '%', change: '+3%' },
144
+ ]);
145
+ props.stats = stats.map((s) => ({
146
+ label: isNonEmptyString(s.label) ? s.label : '指标',
147
+ value: isNonEmptyString(s.value) ? s.value : '0',
148
+ unit: isNonEmptyString(s.unit) ? s.unit : '%',
149
+ change: isNonEmptyString(s.change) ? s.change : '',
150
+ }));
151
+ return;
152
+ }
153
+ // chart
154
+ if (layout === 'chart_v1') {
155
+ ensureTitle(props, '数据趋势');
156
+ const rawLabels = asStringArray(props.labels);
157
+ const rawData = Array.isArray(props.data) ? props.data.filter((n) => typeof n === 'number') : [];
158
+ const aligned = alignLabelsAndData(rawLabels, rawData);
159
+ if (aligned.labels.length === 0) {
160
+ aligned.labels = ['Q1', 'Q2', 'Q3', 'Q4'];
161
+ aligned.data = [20, 45, 70, 95];
162
+ }
163
+ props.labels = aligned.labels;
164
+ props.data = aligned.data;
165
+ ensureString(props, 'unit', '%');
166
+ return;
167
+ }
168
+ if (layout === 'chart_v2') {
169
+ ensureTitle(props, '多系列对比');
170
+ let labels = asStringArray(props.labels);
171
+ if (labels.length === 0)
172
+ labels = ['Q1', 'Q2', 'Q3', 'Q4'];
173
+ let datasets = asObjectArray(props.datasets);
174
+ if (datasets.length === 0 && Array.isArray(props.data)) {
175
+ const data = props.data.filter((n) => typeof n === 'number');
176
+ datasets = [{ label: '数据', data: data.length > 0 ? data : [20, 45, 70, 95] }];
177
+ }
178
+ if (datasets.length === 0) {
179
+ datasets = [
180
+ { label: '系列 A', data: [20, 45, 60, 80] },
181
+ { label: '系列 B', data: [15, 35, 55, 75] },
182
+ ];
183
+ }
184
+ props.labels = labels;
185
+ props.datasets = datasets.map((ds) => {
186
+ const data = Array.isArray(ds.data)
187
+ ? ds.data.filter((n) => typeof n === 'number')
188
+ : [];
189
+ const aligned = alignLabelsAndData(labels, data);
190
+ return {
191
+ label: isNonEmptyString(ds.label) ? ds.label : '数据',
192
+ data: aligned.data,
193
+ color: isNonEmptyString(ds.color) ? ds.color : undefined,
194
+ };
195
+ });
196
+ ensureString(props, 'unit', '%');
197
+ return;
198
+ }
199
+ // comparison
200
+ if (layout === 'comparison_v1' || layout === 'comparison_v2') {
201
+ ensureTitle(props, '对比分析');
202
+ ensureString(props, 'leftTitle', layout === 'comparison_v2' ? '优势' : '方案 A');
203
+ ensureString(props, 'rightTitle', layout === 'comparison_v2' ? '劣势' : '方案 B');
204
+ const points = asStringArray(props.points);
205
+ const { left, right } = splitPoints(points, 3);
206
+ const leftPoints = asStringArray(props.leftPoints).length > 0 ? asStringArray(props.leftPoints) : left;
207
+ const rightPoints = asStringArray(props.rightPoints).length > 0 ? asStringArray(props.rightPoints) : right;
208
+ props.leftPoints = leftPoints.length > 0 ? leftPoints : ['对比点 A', '对比点 B'];
209
+ props.rightPoints = rightPoints.length > 0 ? rightPoints : ['对比点 C', '对比点 D'];
210
+ return;
211
+ }
212
+ if (layout === 'comparison_v3') {
213
+ ensureTitle(props, '对比分析');
214
+ ensureString(props, 'leftTitle', '方案 A');
215
+ ensureString(props, 'rightTitle', '方案 B');
216
+ const rows = asObjectArray(props.rows);
217
+ if (rows.length === 0) {
218
+ const leftPoints = asStringArray(props.leftPoints);
219
+ const rightPoints = asStringArray(props.rightPoints);
220
+ const count = Math.max(leftPoints.length, rightPoints.length, 3);
221
+ props.rows = Array.from({ length: count }, (_, i) => ({
222
+ feature: `维度 ${i + 1}`,
223
+ left: leftPoints[i] ?? '支持',
224
+ right: rightPoints[i] ?? '待完善',
225
+ }));
226
+ }
227
+ else {
228
+ props.rows = rows.map((r) => ({
229
+ feature: isNonEmptyString(r.feature) ? r.feature : '维度',
230
+ left: isNonEmptyString(r.left) ? r.left : '',
231
+ right: isNonEmptyString(r.right) ? r.right : '',
232
+ }));
233
+ }
234
+ return;
235
+ }
236
+ // pricing
237
+ if (layout === 'pricing_v1') {
238
+ ensureTitle(props, '价格方案');
239
+ let tiers = asObjectArray(props.tiers);
240
+ if (tiers.length === 0)
241
+ tiers = asObjectArray(props.plans);
242
+ if (tiers.length === 0) {
243
+ tiers = [
244
+ { name: '基础版', price: '¥99', period: '/月', features: ['核心功能', '邮件支持'], cta: '选择基础版' },
245
+ { name: '专业版', price: '¥299', period: '/月', features: ['全部功能', '优先支持', '数据分析'], cta: '选择专业版' },
246
+ { name: '企业版', price: '定制', period: '', features: ['私有化部署', '专属客户经理', 'SLA 保障'], cta: '联系我们' },
247
+ ];
248
+ }
249
+ props.tiers = tiers.map((t) => ({
250
+ name: isNonEmptyString(t.name) ? t.name : '方案',
251
+ price: isNonEmptyString(t.price) ? t.price : '¥0',
252
+ period: isNonEmptyString(t.period) ? t.period : '/月',
253
+ features: asStringArray(t.features).length > 0 ? asStringArray(t.features) : ['基础功能'],
254
+ cta: isNonEmptyString(t.cta) ? t.cta : '立即选择',
255
+ }));
256
+ return;
257
+ }
258
+ if (layout === 'pricing_v2') {
259
+ ensureTitle(props, '价格方案');
260
+ let plans = asObjectArray(props.plans);
261
+ if (plans.length === 0)
262
+ plans = asObjectArray(props.tiers);
263
+ if (plans.length === 0) {
264
+ plans = [
265
+ { name: '基础版', price: '¥99', period: '/月', features: ['核心功能', '邮件支持'] },
266
+ { name: '专业版', price: '¥299', period: '/月', features: ['全部功能', '优先支持', '数据分析'], highlighted: true },
267
+ { name: '企业版', price: '定制', period: '', features: ['私有化部署', '专属客户经理', 'SLA 保障'] },
268
+ ];
269
+ }
270
+ props.plans = plans.map((p) => ({
271
+ name: isNonEmptyString(p.name) ? p.name : '方案',
272
+ price: isNonEmptyString(p.price) ? p.price : '¥0',
273
+ period: isNonEmptyString(p.period) ? p.period : '/月',
274
+ features: asStringArray(p.features).length > 0 ? asStringArray(p.features) : ['基础功能'],
275
+ highlighted: typeof p.highlighted === 'boolean' ? p.highlighted : false,
276
+ }));
277
+ return;
278
+ }
279
+ // process
280
+ if (layout === 'process_v1') {
281
+ ensureTitle(props, '实施流程');
282
+ const steps = asObjectArray(props.steps);
283
+ if (steps.length > 0) {
284
+ props.steps = steps.map((s) => objectToStringStep(s));
285
+ }
286
+ else {
287
+ const stringSteps = asStringArray(props.steps);
288
+ props.steps = stringSteps.length > 0 ? stringSteps : ['需求调研', '方案落地', '持续运营'];
289
+ }
290
+ return;
291
+ }
292
+ if (layout === 'process_v2' || layout === 'process_v3') {
293
+ ensureTitle(props, '实施流程');
294
+ const steps = asObjectArray(props.steps);
295
+ if (steps.length > 0) {
296
+ props.steps = steps.map((s) => ({
297
+ title: isNonEmptyString(s.title) ? s.title : objectToStringStep(s),
298
+ description: isNonEmptyString(s.description) ? s.description : '',
299
+ }));
300
+ }
301
+ else {
302
+ const stringSteps = asStringArray(props.steps);
303
+ props.steps = stringSteps.length > 0
304
+ ? stringSteps.map(stringToObjectStep)
305
+ : [
306
+ { title: '需求调研', description: '梳理业务场景与关键痛点' },
307
+ { title: '方案落地', description: '快速部署并接入现有工作流' },
308
+ { title: '持续运营', description: '基于使用数据不断优化效果' },
309
+ ];
310
+ }
311
+ return;
312
+ }
313
+ // timeline
314
+ if (layout === 'timeline_v1' || layout === 'timeline_v2') {
315
+ ensureTitle(props, '发展历程');
316
+ const milestones = asObjectArray(props.milestones);
317
+ if (milestones.length === 0) {
318
+ props.milestones = [
319
+ { date: '第一阶段', title: '产品立项', description: '验证核心场景与用户需求' },
320
+ { date: '第二阶段', title: '快速迭代', description: '发布 MVP 并收集反馈' },
321
+ { date: '第三阶段', title: '规模化', description: '服务更多客户,完善生态' },
322
+ ];
323
+ }
324
+ else {
325
+ props.milestones = milestones.map((m) => ({
326
+ date: isNonEmptyString(m.date) ? m.date : '',
327
+ title: isNonEmptyString(m.title) ? m.title : '里程碑',
328
+ description: isNonEmptyString(m.description) ? m.description : '',
329
+ }));
330
+ }
331
+ return;
332
+ }
333
+ if (layout === 'timeline_v3') {
334
+ ensureTitle(props, '发展历程');
335
+ const events = asObjectArray(props.events);
336
+ if (events.length === 0) {
337
+ const milestones = asObjectArray(props.milestones);
338
+ if (milestones.length > 0) {
339
+ props.events = milestones.map((m) => ({
340
+ date: isNonEmptyString(m.date) ? m.date : '',
341
+ title: isNonEmptyString(m.title) ? m.title : '事件',
342
+ description: isNonEmptyString(m.description) ? m.description : '',
343
+ }));
344
+ }
345
+ else {
346
+ props.events = [
347
+ { date: '第一阶段', title: '产品立项', description: '验证核心场景与用户需求' },
348
+ { date: '第二阶段', title: '快速迭代', description: '发布 MVP 并收集反馈' },
349
+ { date: '第三阶段', title: '规模化', description: '服务更多客户,完善生态' },
350
+ ];
351
+ }
352
+ }
353
+ else {
354
+ props.events = events.map((e) => ({
355
+ date: isNonEmptyString(e.date) ? e.date : '',
356
+ title: isNonEmptyString(e.title) ? e.title : '事件',
357
+ description: isNonEmptyString(e.description) ? e.description : '',
358
+ }));
359
+ }
360
+ return;
361
+ }
362
+ // roadmap
363
+ if (layout === 'roadmap_v1') {
364
+ ensureTitle(props, '产品路线图');
365
+ const phases = asObjectArray(props.phases);
366
+ if (phases.length > 0 && isNonEmptyString(phases[0]?.title)) {
367
+ props.phases = phases.map((p) => ({
368
+ title: isNonEmptyString(p.title) ? p.title : '阶段',
369
+ description: isNonEmptyString(p.description) ? p.description : '',
370
+ status: isNonEmptyString(p.status) ? p.status : '',
371
+ }));
372
+ }
373
+ else {
374
+ props.phases = [
375
+ { title: '短期', description: '完成核心功能打磨,验证首批客户场景', status: '进行中' },
376
+ { title: '中期', description: '扩展平台能力,提升自动化水平', status: '规划中' },
377
+ { title: '长期', description: '构建生态闭环,成为行业标杆', status: '愿景' },
378
+ ];
379
+ }
380
+ return;
381
+ }
382
+ if (layout === 'roadmap_v2') {
383
+ ensureTitle(props, '产品路线图');
384
+ const phases = asObjectArray(props.phases);
385
+ if (phases.length > 0 && (Array.isArray(phases[0]?.goals) || isNonEmptyString(phases[0]?.phase))) {
386
+ props.phases = phases.map((p) => ({
387
+ phase: isNonEmptyString(p.phase) ? p.phase : isNonEmptyString(p.title) ? p.title : '阶段',
388
+ goals: asStringArray(p.goals).length > 0 ? asStringArray(p.goals) : ['完成关键目标'],
389
+ }));
390
+ }
391
+ else {
392
+ props.phases = [
393
+ { phase: '短期', goals: ['完成核心功能打磨', '验证首批客户场景'] },
394
+ { phase: '中期', goals: ['扩展平台能力', '提升自动化水平'] },
395
+ { phase: '长期', goals: ['构建生态闭环', '成为行业标杆'] },
396
+ ];
397
+ }
398
+ return;
399
+ }
400
+ // quote / testimonial
401
+ if (layout.startsWith('quote') || layout.startsWith('testimonial')) {
402
+ ensureString(props, 'quote', '我们致力于为客户创造可持续的价值。');
403
+ return;
404
+ }
405
+ // content variants
406
+ if (layout === 'content_v1') {
407
+ ensureTitle(props, '内容要点');
408
+ const points = asStringArray(props.points);
409
+ props.points = points.length > 0 ? points : ['要点一', '要点二', '要点三'];
410
+ return;
411
+ }
412
+ if (layout === 'content_v2') {
413
+ ensureTitle(props, '内容要点');
414
+ const points = asStringArray(props.points);
415
+ const { left, right } = splitPoints(points, 3);
416
+ const leftPoints = asStringArray(props.leftPoints).length > 0 ? asStringArray(props.leftPoints) : left;
417
+ const rightPoints = asStringArray(props.rightPoints).length > 0 ? asStringArray(props.rightPoints) : right;
418
+ props.leftPoints = leftPoints.length > 0 ? leftPoints : ['左侧要点一', '左侧要点二'];
419
+ props.rightPoints = rightPoints.length > 0 ? rightPoints : ['右侧要点一', '右侧要点二'];
420
+ return;
421
+ }
422
+ if (layout === 'content_v3' || layout === 'split_v1') {
423
+ ensureTitle(props, '内容要点');
424
+ const points = asStringArray(props.points);
425
+ props.points = points.length > 0 ? points : ['要点一', '要点二', '要点三'];
426
+ if (!isNonEmptyString(props.imageUrl) && !isNonEmptyString(props.image)) {
427
+ props.imageUrl = PLACEHOLDER_IMAGE;
428
+ }
429
+ return;
430
+ }
431
+ if (layout === 'content_v4') {
432
+ ensureTitle(props, '内容要点');
433
+ const cards = asObjectArray(props.cards);
434
+ if (cards.length === 0) {
435
+ const points = asStringArray(props.points);
436
+ props.cards = points.length > 0
437
+ ? points.slice(0, 3).map((p) => ({ title: p, description: '' }))
438
+ : [
439
+ { title: '卡片一', description: '' },
440
+ { title: '卡片二', description: '' },
441
+ { title: '卡片三', description: '' },
442
+ ];
443
+ }
444
+ else {
445
+ props.cards = cards.slice(0, 3).map((c) => ({
446
+ title: isNonEmptyString(c.title) ? c.title : '卡片',
447
+ description: isNonEmptyString(c.description) ? c.description : '',
448
+ }));
449
+ }
450
+ return;
451
+ }
452
+ // faq
453
+ if (layout.startsWith('faq')) {
454
+ ensureTitle(props, '常见问题');
455
+ const items = asObjectArray(props.items);
456
+ if (items.length === 0) {
457
+ props.items = [
458
+ { q: '常见问题一?', a: '这是对应的回答内容。' },
459
+ { q: '常见问题二?', a: '这是对应的回答内容。' },
460
+ ];
461
+ }
462
+ else {
463
+ props.items = items.map((item) => ({
464
+ q: isNonEmptyString(item.q) ? item.q : '问题',
465
+ a: isNonEmptyString(item.a) ? item.a : '回答',
466
+ }));
467
+ }
468
+ return;
469
+ }
470
+ // feature
471
+ if (layout.startsWith('feature')) {
472
+ ensureTitle(props, '产品特性');
473
+ const features = asObjectArray(props.features);
474
+ if (features.length === 0) {
475
+ props.features = [
476
+ { title: '核心能力', description: '支撑关键业务目标高效达成' },
477
+ { title: '差异化优势', description: '形成与竞品显著区隔的能力' },
478
+ { title: '长期价值', description: '持续沉淀资产并释放复利效应' },
479
+ ];
480
+ }
481
+ else {
482
+ props.features = features.slice(0, 4).map((f) => ({
483
+ title: isNonEmptyString(f.title) ? f.title : '特性',
484
+ description: isNonEmptyString(f.description) ? f.description : '',
485
+ icon: isNonEmptyString(f.icon) ? f.icon : undefined,
486
+ }));
487
+ }
488
+ if (layout === 'feature_v3' && !isNonEmptyString(props.image)) {
489
+ props.image = PLACEHOLDER_IMAGE;
490
+ }
491
+ return;
492
+ }
493
+ // team
494
+ if (layout.startsWith('team')) {
495
+ ensureTitle(props, '团队介绍');
496
+ const members = asObjectArray(props.members);
497
+ if (members.length === 0) {
498
+ props.members = [
499
+ { name: '创始人', role: 'CEO', bio: '负责战略与整体方向' },
500
+ { name: '产品负责人', role: 'CPO', bio: '聚焦用户体验与产品迭代' },
501
+ { name: '技术负责人', role: 'CTO', bio: '主导技术架构与工程落地' },
502
+ ];
503
+ }
504
+ else {
505
+ props.members = members.map((m) => ({
506
+ name: isNonEmptyString(m.name) ? m.name : '成员',
507
+ role: isNonEmptyString(m.role) ? m.role : '职位',
508
+ bio: isNonEmptyString(m.bio) ? m.bio : '',
509
+ imageUrl: layout === 'team_v1' ? (isNonEmptyString(m.imageUrl) ? m.imageUrl : PLACEHOLDER_AVATAR) : undefined,
510
+ avatar: layout === 'team_v2' ? (isNonEmptyString(m.avatar) ? m.avatar : isNonEmptyString(m.imageUrl) ? m.imageUrl : PLACEHOLDER_AVATAR) : undefined,
511
+ }));
512
+ }
513
+ return;
514
+ }
515
+ // partners
516
+ if (layout.startsWith('partners')) {
517
+ ensureTitle(props, '合作伙伴');
518
+ const partners = asObjectArray(props.partners);
519
+ if (partners.length === 0) {
520
+ props.partners = [
521
+ { name: '合作伙伴 A', logoUrl: PLACEHOLDER_IMAGE },
522
+ { name: '合作伙伴 B', logoUrl: PLACEHOLDER_IMAGE },
523
+ { name: '合作伙伴 C', logoUrl: PLACEHOLDER_IMAGE },
524
+ ];
525
+ }
526
+ else {
527
+ props.partners = partners.map((p) => ({
528
+ name: isNonEmptyString(p.name) ? p.name : '合作伙伴',
529
+ logoUrl: isNonEmptyString(p.logoUrl) ? p.logoUrl : PLACEHOLDER_IMAGE,
530
+ }));
531
+ }
532
+ return;
533
+ }
534
+ // gallery
535
+ if (layout.startsWith('gallery')) {
536
+ ensureTitle(props, '图片集');
537
+ const images = asObjectArray(props.images);
538
+ if (images.length === 0) {
539
+ props.images = [
540
+ { url: PLACEHOLDER_IMAGE, caption: '' },
541
+ { url: PLACEHOLDER_IMAGE, caption: '' },
542
+ { url: PLACEHOLDER_IMAGE, caption: '' },
543
+ ];
544
+ }
545
+ else {
546
+ props.images = images.map((img) => ({
547
+ url: isNonEmptyString(img.url) ? img.url : PLACEHOLDER_IMAGE,
548
+ caption: isNonEmptyString(img.caption) ? img.caption : '',
549
+ }));
550
+ }
551
+ return;
552
+ }
553
+ // image
554
+ if (layout === 'image_v1') {
555
+ ensureString(props, 'title', '图片标题');
556
+ if (!isNonEmptyString(props.imageUrl))
557
+ props.imageUrl = PLACEHOLDER_COVER;
558
+ return;
559
+ }
560
+ if (layout === 'image_v2') {
561
+ ensureTitle(props, '图文说明');
562
+ if (!isNonEmptyString(props.image))
563
+ props.image = PLACEHOLDER_IMAGE;
564
+ const body = isNonEmptyString(props.body) ? props.body : isNonEmptyString(props.subtitle) ? props.subtitle : '';
565
+ if (!isNonEmptyString(props.body))
566
+ props.body = body || '配合标题补充关键信息';
567
+ return;
568
+ }
569
+ // swot / pest
570
+ if (layout.startsWith('swot')) {
571
+ ensureTitle(props, 'SWOT 分析');
572
+ ensureString(props, 'strength', '核心优势');
573
+ ensureString(props, 'weakness', '待改进点');
574
+ ensureString(props, 'opportunity', '市场机会');
575
+ ensureString(props, 'threat', '潜在风险');
576
+ return;
577
+ }
578
+ if (layout.startsWith('pest')) {
579
+ ensureTitle(props, 'PEST 分析');
580
+ ensureString(props, 'political', '政策环境');
581
+ ensureString(props, 'economic', '经济环境');
582
+ ensureString(props, 'social', '社会环境');
583
+ ensureString(props, 'technological', '技术环境');
584
+ return;
585
+ }
586
+ }
587
+ /**
588
+ * 为 AI 生成的 slide 自动补全关键 props,避免渲染时出现空白页或占位提示。
589
+ * 先按角色兜底,再按具体版式转换数据结构。
590
+ */
591
+ export function patchSlideContent(slide) {
592
+ const props = { ...(slide.props ?? {}) };
593
+ // 1. 角色级兜底(确保常见数组存在)
594
+ switch (slide.role) {
595
+ case 'chart':
596
+ if (slide.layout !== 'chart_v2') {
597
+ ensureArray(props, 'data', [20, 45, 70, 95]);
598
+ }
599
+ ensureArray(props, 'labels', ['Q1', 'Q2', 'Q3', 'Q4']);
600
+ break;
601
+ case 'stats':
602
+ ensureArray(props, 'stats', [{ label: '指标', value: '100', unit: '%' }]);
603
+ break;
604
+ case 'process':
605
+ ensureArray(props, 'steps', ['步骤一', '步骤二', '步骤三']);
606
+ break;
607
+ case 'timeline':
608
+ ensureArray(props, 'milestones', [
609
+ { date: '第一阶段', title: '里程碑', description: '关键进展' },
610
+ ]);
611
+ break;
612
+ case 'roadmap':
613
+ ensureArray(props, 'phases', [{ phase: '近期', goals: ['完成核心功能'] }]);
614
+ break;
615
+ case 'faq':
616
+ ensureArray(props, 'items', [{ q: '常见问题?', a: '这是回答。' }]);
617
+ break;
618
+ case 'feature':
619
+ ensureArray(props, 'features', [{ title: '核心能力', description: '支撑关键业务目标高效达成' }]);
620
+ break;
621
+ case 'team':
622
+ ensureArray(props, 'members', [{ name: '成员', role: '职位', bio: '简介' }]);
623
+ break;
624
+ case 'partners':
625
+ ensureArray(props, 'partners', [{ name: '合作伙伴' }]);
626
+ break;
627
+ case 'pricing':
628
+ ensureArray(props, 'plans', [
629
+ { name: '基础版', price: '¥0', period: '/月', features: ['基础功能'] },
630
+ ]);
631
+ break;
632
+ case 'gallery':
633
+ ensureArray(props, 'images', [{ caption: '图片说明' }]);
634
+ break;
635
+ case 'tableOfContents':
636
+ ensureArray(props, 'items', ['内容一', '内容二', '内容三']);
637
+ break;
638
+ case 'metric':
639
+ ensureArray(props, 'metrics', [{ label: '核心指标', value: '100', unit: '%', change: '' }]);
640
+ break;
641
+ case 'quote':
642
+ case 'testimonial':
643
+ ensureString(props, 'quote', '我们致力于为客户创造可持续的价值。');
644
+ break;
645
+ case 'cover':
646
+ case 'closing':
647
+ ensureString(props, 'title', slide.role === 'cover' ? '演示标题' : '感谢观看');
648
+ break;
649
+ case 'swot':
650
+ ensureString(props, 'strength', '核心优势');
651
+ ensureString(props, 'weakness', '待改进点');
652
+ ensureString(props, 'opportunity', '市场机会');
653
+ ensureString(props, 'threat', '潜在风险');
654
+ break;
655
+ case 'pest':
656
+ ensureString(props, 'political', '政策环境');
657
+ ensureString(props, 'economic', '经济环境');
658
+ ensureString(props, 'social', '社会环境');
659
+ ensureString(props, 'technological', '技术环境');
660
+ break;
661
+ default:
662
+ break;
663
+ }
664
+ // 2. 按版式精确补全/转换
665
+ patchSlideContentForLayout(slide, props);
666
+ return { ...slide, props };
667
+ }
668
+ /**
669
+ * 对 DeckGoal 的每一页自动补全关键内容字段。
670
+ */
671
+ export function patchGoalContent(goal) {
672
+ return {
673
+ ...goal,
674
+ slides: goal.slides.map((slide) => patchSlideContent(slide)),
675
+ };
676
+ }
677
+ //# sourceMappingURL=patch.js.map