@cicctencent/agent-server 0.2.40 → 0.2.42

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.
Files changed (112) hide show
  1. package/README.md +17 -1
  2. package/dist/cjs/agent/agent-server.d.ts.map +1 -1
  3. package/dist/cjs/agent/agent-server.js +11 -86
  4. package/dist/cjs/agent/agent-server.js.map +1 -1
  5. package/dist/cjs/doc-generator/config-loader.d.ts +27 -0
  6. package/dist/cjs/doc-generator/config-loader.d.ts.map +1 -0
  7. package/dist/cjs/doc-generator/config-loader.js +200 -0
  8. package/dist/cjs/doc-generator/config-loader.js.map +1 -0
  9. package/dist/cjs/doc-generator/docx-renderer.d.ts +14 -0
  10. package/dist/cjs/doc-generator/docx-renderer.d.ts.map +1 -0
  11. package/dist/cjs/doc-generator/docx-renderer.js +354 -0
  12. package/dist/cjs/doc-generator/docx-renderer.js.map +1 -0
  13. package/dist/cjs/doc-generator/index.d.ts +23 -0
  14. package/dist/cjs/doc-generator/index.d.ts.map +1 -0
  15. package/dist/cjs/doc-generator/index.js +251 -0
  16. package/dist/cjs/doc-generator/index.js.map +1 -0
  17. package/dist/cjs/doc-generator/pdf-renderer.d.ts +14 -0
  18. package/dist/cjs/doc-generator/pdf-renderer.d.ts.map +1 -0
  19. package/dist/cjs/doc-generator/pdf-renderer.js +378 -0
  20. package/dist/cjs/doc-generator/pdf-renderer.js.map +1 -0
  21. package/dist/cjs/doc-generator/pptx-renderer.d.ts +14 -0
  22. package/dist/cjs/doc-generator/pptx-renderer.d.ts.map +1 -0
  23. package/dist/cjs/doc-generator/pptx-renderer.js +649 -0
  24. package/dist/cjs/doc-generator/pptx-renderer.js.map +1 -0
  25. package/dist/cjs/doc-generator/report-renderer.d.ts +88 -0
  26. package/dist/cjs/doc-generator/report-renderer.d.ts.map +1 -0
  27. package/dist/cjs/doc-generator/report-renderer.js +699 -0
  28. package/dist/cjs/doc-generator/report-renderer.js.map +1 -0
  29. package/dist/cjs/doc-generator/types.d.ts +193 -0
  30. package/dist/cjs/doc-generator/types.d.ts.map +1 -0
  31. package/dist/cjs/doc-generator/types.js +7 -0
  32. package/dist/cjs/doc-generator/types.js.map +1 -0
  33. package/dist/cjs/index.d.ts +4 -0
  34. package/dist/cjs/index.d.ts.map +1 -1
  35. package/dist/cjs/index.js +45 -36
  36. package/dist/cjs/index.js.map +1 -1
  37. package/dist/cjs/prompt/prompt-builder.d.ts +1 -1
  38. package/dist/cjs/prompt/prompt-builder.d.ts.map +1 -1
  39. package/dist/cjs/prompt/prompt-builder.js +14 -1
  40. package/dist/cjs/prompt/prompt-builder.js.map +1 -1
  41. package/dist/cjs/prompt/shared-fragments.d.ts +3 -3
  42. package/dist/cjs/prompt/shared-fragments.d.ts.map +1 -1
  43. package/dist/cjs/prompt/shared-fragments.js +5 -3
  44. package/dist/cjs/prompt/shared-fragments.js.map +1 -1
  45. package/dist/cjs/tool/builtin.d.ts +10 -3
  46. package/dist/cjs/tool/builtin.d.ts.map +1 -1
  47. package/dist/cjs/tool/builtin.js +10 -3
  48. package/dist/cjs/tool/builtin.js.map +1 -1
  49. package/dist/cjs/tool/tools/docgen.d.ts +5 -0
  50. package/dist/cjs/tool/tools/docgen.d.ts.map +1 -0
  51. package/dist/cjs/tool/tools/docgen.js +388 -0
  52. package/dist/cjs/tool/tools/docgen.js.map +1 -0
  53. package/dist/cjs/tool/tools/sandbox.js +2 -2
  54. package/dist/cjs/tool/tools/sandbox.js.map +1 -1
  55. package/dist/cjs/tool/tools/web.js +2 -2
  56. package/dist/cjs/tool/tools/web.js.map +1 -1
  57. package/dist/esm/agent/agent-server.d.ts.map +1 -1
  58. package/dist/esm/agent/agent-server.js +11 -86
  59. package/dist/esm/agent/agent-server.js.map +1 -1
  60. package/dist/esm/doc-generator/config-loader.d.ts +27 -0
  61. package/dist/esm/doc-generator/config-loader.d.ts.map +1 -0
  62. package/dist/esm/doc-generator/config-loader.js +160 -0
  63. package/dist/esm/doc-generator/config-loader.js.map +1 -0
  64. package/dist/esm/doc-generator/docx-renderer.d.ts +14 -0
  65. package/dist/esm/doc-generator/docx-renderer.d.ts.map +1 -0
  66. package/dist/esm/doc-generator/docx-renderer.js +318 -0
  67. package/dist/esm/doc-generator/docx-renderer.js.map +1 -0
  68. package/dist/esm/doc-generator/index.d.ts +23 -0
  69. package/dist/esm/doc-generator/index.d.ts.map +1 -0
  70. package/dist/esm/doc-generator/index.js +212 -0
  71. package/dist/esm/doc-generator/index.js.map +1 -0
  72. package/dist/esm/doc-generator/pdf-renderer.d.ts +14 -0
  73. package/dist/esm/doc-generator/pdf-renderer.d.ts.map +1 -0
  74. package/dist/esm/doc-generator/pdf-renderer.js +339 -0
  75. package/dist/esm/doc-generator/pdf-renderer.js.map +1 -0
  76. package/dist/esm/doc-generator/pptx-renderer.d.ts +14 -0
  77. package/dist/esm/doc-generator/pptx-renderer.d.ts.map +1 -0
  78. package/dist/esm/doc-generator/pptx-renderer.js +643 -0
  79. package/dist/esm/doc-generator/pptx-renderer.js.map +1 -0
  80. package/dist/esm/doc-generator/report-renderer.d.ts +88 -0
  81. package/dist/esm/doc-generator/report-renderer.d.ts.map +1 -0
  82. package/dist/esm/doc-generator/report-renderer.js +663 -0
  83. package/dist/esm/doc-generator/report-renderer.js.map +1 -0
  84. package/dist/esm/doc-generator/types.d.ts +193 -0
  85. package/dist/esm/doc-generator/types.d.ts.map +1 -0
  86. package/dist/esm/doc-generator/types.js +6 -0
  87. package/dist/esm/doc-generator/types.js.map +1 -0
  88. package/dist/esm/index.d.ts +4 -0
  89. package/dist/esm/index.d.ts.map +1 -1
  90. package/dist/esm/index.js +3 -0
  91. package/dist/esm/index.js.map +1 -1
  92. package/dist/esm/prompt/prompt-builder.d.ts +1 -1
  93. package/dist/esm/prompt/prompt-builder.d.ts.map +1 -1
  94. package/dist/esm/prompt/prompt-builder.js +14 -1
  95. package/dist/esm/prompt/prompt-builder.js.map +1 -1
  96. package/dist/esm/prompt/shared-fragments.d.ts +3 -3
  97. package/dist/esm/prompt/shared-fragments.d.ts.map +1 -1
  98. package/dist/esm/prompt/shared-fragments.js +5 -3
  99. package/dist/esm/prompt/shared-fragments.js.map +1 -1
  100. package/dist/esm/tool/builtin.d.ts +10 -3
  101. package/dist/esm/tool/builtin.d.ts.map +1 -1
  102. package/dist/esm/tool/builtin.js +10 -3
  103. package/dist/esm/tool/builtin.js.map +1 -1
  104. package/dist/esm/tool/tools/docgen.d.ts +5 -0
  105. package/dist/esm/tool/tools/docgen.d.ts.map +1 -0
  106. package/dist/esm/tool/tools/docgen.js +352 -0
  107. package/dist/esm/tool/tools/docgen.js.map +1 -0
  108. package/dist/esm/tool/tools/sandbox.js +2 -2
  109. package/dist/esm/tool/tools/sandbox.js.map +1 -1
  110. package/dist/esm/tool/tools/web.js +2 -2
  111. package/dist/esm/tool/tools/web.js.map +1 -1
  112. package/package.json +7 -2
@@ -0,0 +1,649 @@
1
+ "use strict";
2
+ /**
3
+ * @file PPTX 渲染器(视觉升级版)
4
+ * @description 使用 pptxgenjs 将结构化幻灯片数据渲染为精美 .pptx 文件
5
+ * 包含装饰元素、卡片容器、多种布局变体,提升视觉质量
6
+ */
7
+ var __importDefault = (this && this.__importDefault) || function (mod) {
8
+ return (mod && mod.__esModule) ? mod : { "default": mod };
9
+ };
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.renderPptx = renderPptx;
12
+ const pptxgenjs_1 = __importDefault(require("pptxgenjs"));
13
+ const config_loader_ts_1 = require("./config-loader.js");
14
+ // ============================================================
15
+ // 辅助函数
16
+ // ============================================================
17
+ /** 将 hex 颜色转为 pptxgenjs 格式(去掉 #) */
18
+ function hex(color) {
19
+ if (!color)
20
+ return '000000';
21
+ return color.replace('#', '');
22
+ }
23
+ /** 加深颜色(factor: 0=不变, 1=纯黑) */
24
+ function darkenColor(color, factor) {
25
+ const h = hex(color);
26
+ const r = Math.round(parseInt(h.substring(0, 2), 16) * (1 - factor));
27
+ const g = Math.round(parseInt(h.substring(2, 4), 16) * (1 - factor));
28
+ const b = Math.round(parseInt(h.substring(4, 6), 16) * (1 - factor));
29
+ return [r, g, b].map(v => Math.min(255, Math.max(0, v)).toString(16).padStart(2, '0')).join('');
30
+ }
31
+ /** 创建卡片阴影(每次新建对象,避免 pptxgenjs 内部修改) */
32
+ function cardShadow() {
33
+ return { type: 'outer', color: '000000', blur: 4, offset: 1, angle: 135, opacity: 0.08 };
34
+ }
35
+ /** 绘制带阴影的卡片容器 */
36
+ function addCard(slide, pos, theme, opts) {
37
+ slide.addShape('rect', {
38
+ x: pos.x, y: pos.y, w: pos.w, h: pos.h,
39
+ fill: { color: opts?.fill || hex(theme.colors.background) || 'FFFFFF' },
40
+ shadow: opts?.noShadow ? undefined : cardShadow(),
41
+ line: { color: hex(theme.colors.border) || 'E0E0E0', width: 0.5 },
42
+ });
43
+ }
44
+ /** 添加装饰性半透明圆(视觉母题) */
45
+ function addDecoCircles(slide, baseX, baseY, colors) {
46
+ const configs = [
47
+ { dx: 0, dy: 0, size: 2.2, colorIdx: 0, transparency: 80 },
48
+ { dx: 0.9, dy: -0.6, size: 1.4, colorIdx: 1, transparency: 75 },
49
+ { dx: -0.5, dy: 0.7, size: 1.0, colorIdx: 2, transparency: 85 },
50
+ ];
51
+ for (const cfg of configs) {
52
+ slide.addShape('ellipse', {
53
+ x: baseX + cfg.dx, y: baseY + cfg.dy,
54
+ w: cfg.size, h: cfg.size,
55
+ fill: { color: hex(colors[cfg.colorIdx % colors.length]), transparency: cfg.transparency },
56
+ line: { width: 0 },
57
+ });
58
+ }
59
+ }
60
+ /** 添加页码(右下角) */
61
+ function addPageNumber(slide, theme) {
62
+ try {
63
+ slide.slideNumber = {
64
+ x: 9.2, y: 5.25, w: 0.6, h: 0.25,
65
+ fontSize: 8,
66
+ color: hex(theme.colors.textSecondary) || '999999',
67
+ };
68
+ }
69
+ catch {
70
+ // slideNumber 可能在不同版本有差异,静默降级
71
+ }
72
+ }
73
+ /** 浅色背景色(用于卡片底色) */
74
+ function cardBg(theme) {
75
+ return hex(theme.colors.background) || 'F8F9FA';
76
+ }
77
+ /** 根据 icon 类型返回 Unicode bullet 字符码 */
78
+ function bulletCharCode(icon) {
79
+ switch (icon) {
80
+ case 'check': return '2713'; // ✓
81
+ case 'cross': return '2717'; // ✗
82
+ case 'arrow': return '25B6'; // ▶
83
+ case 'diamond': return '25C6'; // ◆
84
+ default: return '25CF'; // ●
85
+ }
86
+ }
87
+ // ============================================================
88
+ // 主渲染函数
89
+ // ============================================================
90
+ /** 渲染 PPTX */
91
+ async function renderPptx(data, templatesDir) {
92
+ const config = (0, config_loader_ts_1.loadDocConfig)('pptx', templatesDir);
93
+ const theme = (0, config_loader_ts_1.loadTheme)('pptx', data.theme, templatesDir);
94
+ const pptx = new pptxgenjs_1.default();
95
+ if (config.slideSize) {
96
+ pptx.defineLayout({ name: 'CUSTOM', width: config.slideSize.width, height: config.slideSize.height });
97
+ pptx.layout = 'CUSTOM';
98
+ }
99
+ pptx.title = data.title;
100
+ pptx.author = 'WorkAI';
101
+ const totalSlides = data.slides.length;
102
+ for (let i = 0; i < totalSlides; i++) {
103
+ const slide = pptx.addSlide();
104
+ renderSlide(slide, data.slides[i], theme, i, totalSlides);
105
+ }
106
+ const buffer = await pptx.write({ outputType: 'nodebuffer' });
107
+ const filename = data.filename || `${sanitizeFilename(data.title)}.pptx`;
108
+ return {
109
+ buffer,
110
+ filename,
111
+ mimeType: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
112
+ size: buffer.length,
113
+ };
114
+ }
115
+ // ============================================================
116
+ // Slide 类型渲染
117
+ // ============================================================
118
+ function renderSlide(slide, def, theme, index, total) {
119
+ const c = theme.colors;
120
+ const f = theme.fonts;
121
+ const s = theme.sizes;
122
+ const sp = theme.spacing;
123
+ const margin = sp.margin;
124
+ const isCover = index === 0;
125
+ const isClosing = index === total - 1 && def.type === 'closing';
126
+ if (def.background) {
127
+ slide.background = { color: hex(def.background) };
128
+ }
129
+ switch (def.type) {
130
+ case 'cover':
131
+ renderCover(slide, def, theme);
132
+ break;
133
+ case 'toc':
134
+ renderToc(slide, def, theme);
135
+ addPageNumber(slide, theme);
136
+ break;
137
+ case 'section':
138
+ renderSection(slide, def, theme);
139
+ break;
140
+ case 'content':
141
+ renderContent(slide, def, theme);
142
+ addPageNumber(slide, theme);
143
+ break;
144
+ case 'two_column':
145
+ renderTwoColumn(slide, def, theme);
146
+ addPageNumber(slide, theme);
147
+ break;
148
+ case 'chart':
149
+ renderChartSlide(slide, def, theme);
150
+ addPageNumber(slide, theme);
151
+ break;
152
+ case 'table':
153
+ renderTableSlide(slide, def, theme);
154
+ addPageNumber(slide, theme);
155
+ break;
156
+ case 'image_text':
157
+ renderImageText(slide, def, theme);
158
+ addPageNumber(slide, theme);
159
+ break;
160
+ case 'stats':
161
+ renderStats(slide, def, theme);
162
+ addPageNumber(slide, theme);
163
+ break;
164
+ case 'closing':
165
+ renderClosing(slide, def, theme);
166
+ break;
167
+ case 'blank':
168
+ default:
169
+ if (def.title) {
170
+ slide.addText(def.title, {
171
+ x: margin, y: margin, w: '90%', h: 0.6,
172
+ fontSize: s.heading, fontFace: f.heading, color: hex(c.heading || c.primary),
173
+ bold: true,
174
+ });
175
+ }
176
+ addPageNumber(slide, theme);
177
+ break;
178
+ }
179
+ }
180
+ // ---- Cover ----
181
+ function renderCover(slide, def, theme) {
182
+ const c = theme.colors;
183
+ const f = theme.fonts;
184
+ const s = theme.sizes;
185
+ const margin = theme.spacing.margin;
186
+ slide.background = { color: hex(c.backgroundDark || c.primary) };
187
+ // 装饰圆(右上角)
188
+ const decoColors = [c.accent, c.secondary || c.primary, c.textOnDark || 'FFFFFF'];
189
+ addDecoCircles(slide, 6.8, -0.3, decoColors);
190
+ // 标题(左对齐,大字号)
191
+ slide.addText(def.title || '', {
192
+ x: margin + 0.3, y: 1.3, w: 6, h: 1.5,
193
+ fontSize: Math.min(s.title + 4, 48), fontFace: f.title, color: hex(c.textOnDark),
194
+ bold: true, align: 'left', margin: 0,
195
+ });
196
+ // Accent 横线
197
+ slide.addShape('rect', {
198
+ x: margin + 0.3, y: 3.0, w: 2.5, h: 0.05,
199
+ fill: { color: hex(c.accent) },
200
+ });
201
+ // 副标题
202
+ if (def.subtitle) {
203
+ slide.addText(def.subtitle, {
204
+ x: margin + 0.3, y: 3.2, w: 6, h: 0.6,
205
+ fontSize: s.subheading || 18, fontFace: f.body, color: hex(c.textOnDark),
206
+ align: 'left', transparency: 25, margin: 0,
207
+ });
208
+ }
209
+ // 底部装饰条
210
+ slide.addShape('rect', {
211
+ x: 0, y: 5.425, w: 10, h: 0.2,
212
+ fill: { color: hex(c.accent) },
213
+ });
214
+ }
215
+ // ---- TOC ----
216
+ function renderToc(slide, def, theme) {
217
+ const c = theme.colors;
218
+ const f = theme.fonts;
219
+ const s = theme.sizes;
220
+ const margin = theme.spacing.margin;
221
+ slide.addText(def.title || '目录', {
222
+ x: margin, y: margin, w: '90%', h: 0.7,
223
+ fontSize: s.heading, fontFace: f.heading, color: hex(c.primary),
224
+ bold: true,
225
+ });
226
+ if (def.items?.length) {
227
+ const tocText = def.items.map((item, i) => {
228
+ const label = typeof item === 'string' ? item : (item?.text ?? '');
229
+ return {
230
+ text: `${String(i + 1).padStart(2, '0')} ${label}\n`,
231
+ options: {
232
+ fontSize: s.body + 2,
233
+ fontFace: f.body,
234
+ color: hex(c.textPrimary),
235
+ lineSpacingMultiple: 1.8,
236
+ bullet: false,
237
+ },
238
+ };
239
+ });
240
+ slide.addText(tocText, {
241
+ x: margin + 0.3, y: margin + 0.9, w: '80%', h: 4,
242
+ valign: 'top',
243
+ });
244
+ }
245
+ }
246
+ // ---- Section ----
247
+ function renderSection(slide, def, theme) {
248
+ const c = theme.colors;
249
+ const f = theme.fonts;
250
+ const s = theme.sizes;
251
+ const darkerPrimary = darkenColor(c.primary, 0.2);
252
+ const lighterPrimary = c.secondary || c.primary;
253
+ // 左侧深色面板 (40%)
254
+ slide.addShape('rect', {
255
+ x: 0, y: 0, w: 4, h: 5.625,
256
+ fill: { color: hex(darkerPrimary) },
257
+ });
258
+ // 右侧浅色面板 (60%)
259
+ slide.addShape('rect', {
260
+ x: 4, y: 0, w: 6, h: 5.625,
261
+ fill: { color: hex(lighterPrimary) },
262
+ });
263
+ // 标题(左侧,居中)
264
+ slide.addText(def.title || '', {
265
+ x: 0.5, y: 1.8, w: 3.2, h: 2,
266
+ fontSize: s.title - 4, fontFace: f.heading, color: hex(c.textOnDark),
267
+ bold: true, align: 'left', valign: 'middle', margin: 0,
268
+ });
269
+ // 装饰线(分割线上方)
270
+ slide.addShape('rect', {
271
+ x: 0.5, y: 1.6, w: 1.5, h: 0.04,
272
+ fill: { color: hex(c.accent) },
273
+ });
274
+ // 副标题(右侧,居中)
275
+ if (def.subtitle) {
276
+ slide.addText(def.subtitle, {
277
+ x: 4.5, y: 1.8, w: 5, h: 2,
278
+ fontSize: s.body + 2, fontFace: f.body, color: hex(c.textOnDark),
279
+ align: 'left', valign: 'middle', transparency: 15, margin: 0,
280
+ });
281
+ }
282
+ // 右侧装饰圆
283
+ const decoColors = [c.accent, c.textOnDark || 'FFFFFF', darkerPrimary];
284
+ addDecoCircles(slide, 7.5, 3.5, decoColors);
285
+ }
286
+ // ---- Content ----
287
+ function renderContent(slide, def, theme) {
288
+ const c = theme.colors;
289
+ const f = theme.fonts;
290
+ const s = theme.sizes;
291
+ const sp = theme.spacing;
292
+ const margin = sp.margin;
293
+ // 左侧竖条 accent bar
294
+ slide.addShape('rect', {
295
+ x: 0, y: 0, w: 0.06, h: 5.625,
296
+ fill: { color: hex(c.accent) },
297
+ });
298
+ // 标题(无 accent line)
299
+ slide.addText(def.title || '', {
300
+ x: margin + 0.1, y: margin, w: '88%', h: 0.6,
301
+ fontSize: s.heading, fontFace: f.heading, color: hex(c.heading || c.primary),
302
+ bold: true, margin: 0,
303
+ });
304
+ // 内容区域卡片背景
305
+ const cardY = margin + 0.75;
306
+ const cardH = 4.2;
307
+ addCard(slide, { x: margin, y: cardY, w: 9 - margin * 2, h: cardH }, theme, {
308
+ fill: cardBg(theme),
309
+ });
310
+ if (def.text) {
311
+ slide.addText(def.text, {
312
+ x: margin + 0.25, y: cardY + 0.2, w: 9 - margin * 2 - 0.5, h: cardH - 0.4,
313
+ fontSize: s.body, fontFace: f.body, color: hex(c.textPrimary),
314
+ valign: 'top', lineSpacingMultiple: sp.lineSpacing || 1.3,
315
+ });
316
+ }
317
+ else if (def.bullets?.length || def.items?.length) {
318
+ const bulletList = def.bullets?.length ? def.bullets : (def.items || []);
319
+ const bulletItems = bulletList.map((b) => {
320
+ const text = typeof b === 'string' ? b : (b?.text ?? '');
321
+ const level = typeof b === 'object' && typeof b.level === 'number' ? b.level : 0;
322
+ return {
323
+ text,
324
+ options: {
325
+ fontSize: s.body, fontFace: f.body, color: hex(c.textPrimary),
326
+ bullet: { characterCode: bulletCharCode(def.icon) },
327
+ indentLevel: level,
328
+ lineSpacingMultiple: 1.5,
329
+ paraSpaceBefore: 6,
330
+ },
331
+ };
332
+ });
333
+ slide.addText(bulletItems, {
334
+ x: margin + 0.4, y: cardY + 0.2, w: 9 - margin * 2 - 0.7, h: cardH - 0.4,
335
+ valign: 'top',
336
+ });
337
+ }
338
+ }
339
+ // ---- Two Column ----
340
+ function renderTwoColumn(slide, def, theme) {
341
+ const c = theme.colors;
342
+ const f = theme.fonts;
343
+ const s = theme.sizes;
344
+ const margin = theme.spacing.margin;
345
+ slide.addText(def.title || '', {
346
+ x: margin, y: margin, w: '90%', h: 0.6,
347
+ fontSize: s.heading, fontFace: f.heading, color: hex(c.heading || c.primary),
348
+ bold: true,
349
+ });
350
+ const colW = 4.0;
351
+ const colH = 3.9;
352
+ const colY = margin + 0.8;
353
+ // 左列卡片
354
+ addCard(slide, { x: margin, y: colY, w: colW, h: colH }, theme, { fill: cardBg(theme) });
355
+ renderColumn(slide, def.left, { x: margin + 0.2, y: colY + 0.15, w: colW - 0.4, h: colH - 0.3 }, theme);
356
+ // 中间分隔线
357
+ slide.addShape('rect', {
358
+ x: 4.95, y: colY + 0.3, w: 0.02, h: colH - 0.6,
359
+ fill: { color: hex(c.border) || 'E0E0E0' },
360
+ });
361
+ // 右列卡片
362
+ addCard(slide, { x: 5.2, y: colY, w: colW, h: colH }, theme, { fill: cardBg(theme) });
363
+ renderColumn(slide, def.right, { x: 5.4, y: colY + 0.15, w: colW - 0.4, h: colH - 0.3 }, theme);
364
+ }
365
+ // ---- Chart ----
366
+ function renderChartSlide(slide, def, theme) {
367
+ const c = theme.colors;
368
+ const f = theme.fonts;
369
+ const s = theme.sizes;
370
+ const margin = theme.spacing.margin;
371
+ slide.addText(def.title || '', {
372
+ x: margin, y: margin, w: '90%', h: 0.6,
373
+ fontSize: s.heading, fontFace: f.heading, color: hex(c.heading || c.primary),
374
+ bold: true,
375
+ });
376
+ // 图表卡片背景
377
+ if (def.chart) {
378
+ const cardY = margin + 0.7;
379
+ const cardH = 4.1;
380
+ addCard(slide, { x: margin - 0.1, y: cardY, w: 9 - margin * 2 + 0.2, h: cardH }, theme, {
381
+ fill: 'FFFFFF',
382
+ });
383
+ renderChart(slide, def.chart, theme);
384
+ }
385
+ }
386
+ // ---- Table ----
387
+ function renderTableSlide(slide, def, theme) {
388
+ const c = theme.colors;
389
+ const f = theme.fonts;
390
+ const s = theme.sizes;
391
+ const margin = theme.spacing.margin;
392
+ slide.addText(def.title || '', {
393
+ x: margin, y: margin, w: '90%', h: 0.6,
394
+ fontSize: s.heading, fontFace: f.heading, color: hex(c.heading || c.primary),
395
+ bold: true,
396
+ });
397
+ if (def.table) {
398
+ renderTable(slide, def.table, theme);
399
+ }
400
+ }
401
+ // ---- Image + Text ----
402
+ function renderImageText(slide, def, theme) {
403
+ const c = theme.colors;
404
+ const f = theme.fonts;
405
+ const s = theme.sizes;
406
+ const margin = theme.spacing.margin;
407
+ slide.addText(def.title || '', {
408
+ x: margin, y: margin, w: '90%', h: 0.6,
409
+ fontSize: s.heading, fontFace: f.heading, color: hex(c.heading || c.primary),
410
+ bold: true,
411
+ });
412
+ if (def.text) {
413
+ slide.addText(def.text, {
414
+ x: margin, y: margin + 0.8, w: '52%', h: 4,
415
+ fontSize: s.body, fontFace: f.body, color: hex(c.textPrimary),
416
+ valign: 'top', lineSpacingMultiple: theme.spacing.lineSpacing || 1.3,
417
+ });
418
+ }
419
+ if (def.image_url) {
420
+ // 图片加阴影
421
+ const imgX = 5.8;
422
+ const imgY = margin + 0.8;
423
+ const imgW = 3.7;
424
+ const imgH = 3.5;
425
+ // 图片阴影背景
426
+ slide.addShape('rect', {
427
+ x: imgX - 0.05, y: imgY - 0.05, w: imgW + 0.1, h: imgH + 0.1,
428
+ fill: { color: 'FFFFFF' },
429
+ shadow: cardShadow(),
430
+ line: { color: hex(c.border) || 'E0E0E0', width: 0.5 },
431
+ });
432
+ slide.addImage({
433
+ path: def.image_url,
434
+ x: imgX, y: imgY, w: imgW, h: imgH,
435
+ });
436
+ }
437
+ }
438
+ // ---- Stats ----
439
+ function renderStats(slide, def, theme) {
440
+ const c = theme.colors;
441
+ const f = theme.fonts;
442
+ const s = theme.sizes;
443
+ const margin = theme.spacing.margin;
444
+ slide.addText(def.title || '', {
445
+ x: margin, y: margin, w: '90%', h: 0.6,
446
+ fontSize: s.heading, fontFace: f.heading, color: hex(c.heading || c.primary),
447
+ bold: true,
448
+ });
449
+ if (def.stats?.length) {
450
+ const count = def.stats.length;
451
+ const gap = 0.3;
452
+ const totalW = 9 - margin * 2;
453
+ const cardW = (totalW - gap * (count - 1)) / count;
454
+ const cardY = 1.5;
455
+ const cardH = 2.8;
456
+ def.stats.forEach((stat, i) => {
457
+ const cardX = margin + i * (cardW + gap);
458
+ // 卡片背景
459
+ addCard(slide, { x: cardX, y: cardY, w: cardW, h: cardH }, theme, { fill: 'FFFFFF' });
460
+ // 顶部 accent 条
461
+ slide.addShape('rect', {
462
+ x: cardX, y: cardY, w: cardW, h: 0.06,
463
+ fill: { color: hex(stat.color || c.accent) },
464
+ });
465
+ // 数值(大号居中)
466
+ slide.addText(String(stat.value), {
467
+ x: cardX, y: cardY + 0.5, w: cardW, h: 1.2,
468
+ fontSize: s.statValue || 56, fontFace: f.heading,
469
+ color: hex(stat.color || c.accent), bold: true, align: 'center',
470
+ });
471
+ // 标签
472
+ slide.addText(stat.label, {
473
+ x: cardX + 0.1, y: cardY + 1.8, w: cardW - 0.2, h: 0.6,
474
+ fontSize: s.caption + 2, fontFace: f.body,
475
+ color: hex(c.textSecondary), align: 'center', valign: 'top',
476
+ });
477
+ });
478
+ }
479
+ }
480
+ // ---- Closing ----
481
+ function renderClosing(slide, def, theme) {
482
+ const c = theme.colors;
483
+ const f = theme.fonts;
484
+ const s = theme.sizes;
485
+ slide.background = { color: hex(c.backgroundDark || c.primary) };
486
+ // 装饰圆(左上方,与 cover 呼应)
487
+ const decoColors = [c.accent, c.secondary || c.primary, c.textOnDark || 'FFFFFF'];
488
+ addDecoCircles(slide, -0.5, -0.5, decoColors);
489
+ // 标题(居中)
490
+ slide.addText(def.title || 'Thank You', {
491
+ x: 1, y: 1.6, w: 8, h: 1.2,
492
+ fontSize: s.title, fontFace: f.title, color: hex(c.textOnDark),
493
+ bold: true, align: 'center',
494
+ });
495
+ // 装饰线
496
+ slide.addShape('rect', {
497
+ x: 3.5, y: 3.0, w: 3, h: 0.05,
498
+ fill: { color: hex(c.accent) },
499
+ });
500
+ if (def.subtitle) {
501
+ slide.addText(def.subtitle, {
502
+ x: 1.5, y: 3.2, w: 7, h: 0.6,
503
+ fontSize: s.body, fontFace: f.body, color: hex(c.textOnDark),
504
+ align: 'center', transparency: 20,
505
+ });
506
+ }
507
+ // 底部装饰条
508
+ slide.addShape('rect', {
509
+ x: 0, y: 5.425, w: 10, h: 0.2,
510
+ fill: { color: hex(c.accent) },
511
+ });
512
+ }
513
+ // ============================================================
514
+ // 子组件渲染
515
+ // ============================================================
516
+ function renderColumn(slide, content, pos, theme) {
517
+ if (!content)
518
+ return;
519
+ const c = theme.colors;
520
+ const f = theme.fonts;
521
+ const s = theme.sizes;
522
+ let yOffset = pos.y;
523
+ if (content.title) {
524
+ slide.addText(content.title, {
525
+ x: pos.x, y: yOffset, w: pos.w, h: 0.4,
526
+ fontSize: s.subheading || 16, fontFace: f.heading, color: hex(c.primary),
527
+ bold: true, margin: 0,
528
+ });
529
+ yOffset += 0.5;
530
+ }
531
+ if (content.text) {
532
+ slide.addText(content.text, {
533
+ x: pos.x, y: yOffset, w: pos.w, h: pos.h - (yOffset - pos.y),
534
+ fontSize: s.body, fontFace: f.body, color: hex(c.textPrimary),
535
+ valign: 'top',
536
+ });
537
+ }
538
+ else if (content.bullets?.length) {
539
+ const charCode = bulletCharCode(content.icon);
540
+ const items = content.bullets.map((b) => {
541
+ const text = typeof b === 'string' ? b : (b?.text ?? '');
542
+ const level = typeof b === 'object' && typeof b.level === 'number' ? b.level : 0;
543
+ return {
544
+ text,
545
+ options: {
546
+ fontSize: s.body, fontFace: f.body, color: hex(c.textPrimary),
547
+ bullet: { characterCode: charCode },
548
+ indentLevel: level,
549
+ lineSpacingMultiple: 1.4,
550
+ paraSpaceBefore: 4,
551
+ },
552
+ };
553
+ });
554
+ slide.addText(items, {
555
+ x: pos.x, y: yOffset, w: pos.w, h: pos.h - (yOffset - pos.y),
556
+ valign: 'top',
557
+ });
558
+ }
559
+ }
560
+ function renderChart(slide, chart, theme) {
561
+ const chartColors = theme.colors.chartColors || ['1E2761', 'F96167', '4CAF50', 'FF9800'];
562
+ const margin = theme.spacing.margin;
563
+ // 数据验证:确保 datasets 和 labels 非空
564
+ if (!chart.datasets?.length || !chart.labels?.length) {
565
+ slide.addText('图表数据为空,请检查数据源', {
566
+ x: margin + 0.1, y: margin + 2.0, w: 9 - margin * 2 - 0.2, h: 0.6,
567
+ fontSize: 12, color: '999999', align: 'center',
568
+ });
569
+ return;
570
+ }
571
+ // 过滤掉 data 为空的 dataset
572
+ const validDatasets = chart.datasets.filter(ds => ds.data?.length > 0);
573
+ if (validDatasets.length === 0) {
574
+ slide.addText('图表数据为空,请检查数据源', {
575
+ x: margin + 0.1, y: margin + 2.0, w: 9 - margin * 2 - 0.2, h: 0.6,
576
+ fontSize: 12, color: '999999', align: 'center',
577
+ });
578
+ return;
579
+ }
580
+ const typeMap = {
581
+ bar: 'bar', line: 'line', pie: 'pie', doughnut: 'doughnut', area: 'area',
582
+ };
583
+ const chartType = typeMap[chart.type] || 'bar';
584
+ const chartData = validDatasets.map((ds, i) => ({
585
+ name: ds.label || `Series ${i + 1}`,
586
+ labels: chart.labels,
587
+ values: ds.data,
588
+ }));
589
+ const isPie = chartType === 'pie' || chartType === 'doughnut';
590
+ // Pie/doughnut: need one color per label (slice), not per dataset
591
+ const pieColors = isPie
592
+ ? (validDatasets[0]?.data || []).map((_, i) => chartColors[i % chartColors.length])
593
+ : undefined;
594
+ const chartOpts = {
595
+ x: margin + 0.1, y: margin + 0.9, w: 9 - margin * 2 - 0.2, h: 3.7,
596
+ showTitle: false,
597
+ showLegend: isPie ? true : validDatasets.length > 1,
598
+ legendPos: 'b',
599
+ showValue: chart.type === 'bar' || chart.type === 'line',
600
+ dataLabelPosition: isPie ? 'bestFit' : 'outEnd',
601
+ chartColors: isPie ? pieColors : validDatasets.map((ds, i) => ds.color || chartColors[i % chartColors.length]),
602
+ // 现代化图表样式
603
+ catAxisLabelColor: hex(theme.colors.textSecondary) || '666666',
604
+ valAxisLabelColor: hex(theme.colors.textSecondary) || '666666',
605
+ valGridLine: { color: 'E8E8E8', size: 0.5 },
606
+ catGridLine: { style: 'none' },
607
+ };
608
+ slide.addChart(chartType, chartData, chartOpts);
609
+ }
610
+ function renderTable(slide, table, theme) {
611
+ const c = theme.colors;
612
+ const f = theme.fonts;
613
+ const s = theme.sizes;
614
+ const margin = theme.spacing.margin;
615
+ const headerRow = table.headers.map(h => ({
616
+ text: h,
617
+ options: {
618
+ bold: true,
619
+ fontSize: s.caption + 1,
620
+ fontFace: f.body,
621
+ color: hex(c.tableHeaderText || c.textOnDark),
622
+ fill: { color: hex(c.tableHeader || c.primary) },
623
+ align: 'center',
624
+ valign: 'middle',
625
+ },
626
+ }));
627
+ const dataRows = table.rows.map((row, rowIdx) => row.map(cell => ({
628
+ text: String(cell),
629
+ options: {
630
+ fontSize: s.caption,
631
+ fontFace: f.body,
632
+ color: hex(c.textPrimary),
633
+ fill: { color: rowIdx % 2 === 1 ? (hex(c.tableAltRow) || 'F5F7FA') : (hex(c.background) || 'FFFFFF') },
634
+ align: 'left',
635
+ valign: 'middle',
636
+ },
637
+ })));
638
+ slide.addTable([headerRow, ...dataRows], {
639
+ x: margin, y: margin + 0.9, w: '90%',
640
+ border: { type: 'solid', pt: 0.5, color: hex(c.border) || 'E0E0E0' },
641
+ colW: table.headers.map(() => (9 - margin * 2) / table.headers.length),
642
+ rowH: 0.38,
643
+ autoPage: false,
644
+ });
645
+ }
646
+ function sanitizeFilename(name) {
647
+ return name.replace(/[^a-zA-Z0-9\u4e00-\u9fff_-]/g, '_').slice(0, 50);
648
+ }
649
+ //# sourceMappingURL=pptx-renderer.js.map