@cicctencent/agent-server 0.2.41 → 0.2.43

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