hanseol-dev 5.0.2-dev.53 → 5.0.2-dev.55

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.
@@ -1 +1 @@
1
- {"version":3,"file":"powerpoint-create-agent.d.ts","sourceRoot":"","sources":["../../../src/agents/office/powerpoint-create-agent.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,YAAY,EAAc,MAAM,sBAAsB,CAAC;AA6ahE,wBAAgB,iCAAiC,IAAI,YAAY,CA2BhE"}
1
+ {"version":3,"file":"powerpoint-create-agent.d.ts","sourceRoot":"","sources":["../../../src/agents/office/powerpoint-create-agent.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,YAAY,EAAc,MAAM,sBAAsB,CAAC;AAiiBhE,wBAAgB,iCAAiC,IAAI,YAAY,CA2BhE"}
@@ -1,23 +1,24 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
1
3
  import { POWERPOINT_CREATE_TOOLS } from '../../tools/office/powerpoint-tools.js';
2
- import { resetLayoutCounters } from '../../tools/office/powerpoint-tools/layout-builders.js';
4
+ import { powerpointClient } from '../../tools/office/powerpoint-client.js';
3
5
  import { SubAgent } from '../common/sub-agent.js';
4
6
  import { getSubAgentPhaseLogger, getSubAgentToolCallLogger } from '../common/sub-agent.js';
5
7
  import { logger } from '../../utils/logger.js';
6
- import { PPT_CREATE_SYSTEM_PROMPT, PPT_CREATE_PLANNING_PROMPT, PPT_CREATE_ENHANCEMENT_PROMPT, PPT_STRUCTURED_PLANNING_PROMPT, buildSlideSystemPrompt, } from './powerpoint-create-prompts.js';
7
- const VALID_SLIDE_TOOLS = new Set([
8
- 'ppt_build_title_slide',
9
- 'ppt_build_layout_a',
10
- 'ppt_build_layout_b',
11
- 'ppt_build_layout_c',
12
- 'ppt_build_layout_d',
13
- 'ppt_build_layout_e',
14
- 'ppt_build_layout_f',
15
- 'ppt_build_closing_slide',
16
- ]);
17
- const VALID_COLOR_SCHEMES = new Set([
18
- 'MODERN_TECH', 'WARM_EXECUTIVE', 'CLEAN_MINIMAL', 'CORPORATE', 'NATURE_FRESH', 'BOLD_MODERN',
19
- ]);
20
- const VALID_DESIGN_STYLES = new Set(['sidebar', 'top_band', 'clean']);
8
+ import { getPlatform } from '../../utils/platform-utils.js';
9
+ import { PPT_CREATE_SYSTEM_PROMPT, PPT_CREATE_PLANNING_PROMPT, PPT_CREATE_ENHANCEMENT_PROMPT, PPT_STRUCTURED_PLANNING_PROMPT, buildSlideHtmlPrompt, } from './powerpoint-create-prompts.js';
10
+ const DEFAULT_DESIGN = {
11
+ primary_color: '#1B2A4A',
12
+ accent_color: '#00D4AA',
13
+ background_color: '#FFFFFF',
14
+ text_color: '#1A1A2E',
15
+ accent_light: '#E8F5F0',
16
+ gradient_end: '#2D5F8A',
17
+ font_title: 'Segoe UI',
18
+ font_body: 'Malgun Gothic',
19
+ mood: 'modern-minimal',
20
+ design_notes: 'Clean gradients, card-based layouts',
21
+ };
21
22
  function extractContent(msg) {
22
23
  const content = msg['content'];
23
24
  if (content && content.trim())
@@ -28,80 +29,142 @@ function extractContent(msg) {
28
29
  return '';
29
30
  }
30
31
  function validatePlan(plan) {
31
- if (!plan.design?.color_scheme || !plan.design?.design_style) {
32
- return 'Missing design.color_scheme or design.design_style';
33
- }
34
- if (!VALID_COLOR_SCHEMES.has(plan.design.color_scheme)) {
35
- return `Invalid color_scheme: ${plan.design.color_scheme}`;
36
- }
37
- if (!VALID_DESIGN_STYLES.has(plan.design.design_style)) {
38
- return `Invalid design_style: ${plan.design.design_style}`;
32
+ if (!plan.design)
33
+ return 'Missing design object';
34
+ if (!plan.design.primary_color || !plan.design.accent_color) {
35
+ return 'Missing design colors (primary_color, accent_color)';
39
36
  }
40
37
  if (!Array.isArray(plan.slides) || plan.slides.length < 3) {
41
38
  return 'slides array must have at least 3 entries';
42
39
  }
43
- if (plan.slides[0]?.tool !== 'ppt_build_title_slide') {
44
- return 'First slide must be ppt_build_title_slide';
40
+ if (plan.slides[0]?.type !== 'title') {
41
+ return 'First slide must be type "title"';
45
42
  }
46
- if (plan.slides[plan.slides.length - 1]?.tool !== 'ppt_build_closing_slide') {
47
- return 'Last slide must be ppt_build_closing_slide';
43
+ if (plan.slides[plan.slides.length - 1]?.type !== 'closing') {
44
+ return 'Last slide must be type "closing"';
48
45
  }
49
46
  for (let i = 0; i < plan.slides.length; i++) {
50
- const s = plan.slides[i];
51
- if (!VALID_SLIDE_TOOLS.has(s.tool)) {
52
- return `Slide ${i + 1}: invalid tool "${s.tool}"`;
53
- }
54
- if (!s.title) {
47
+ if (!plan.slides[i].title) {
55
48
  return `Slide ${i + 1}: missing title`;
56
49
  }
57
50
  }
58
- const counts = {};
59
- for (const s of plan.slides) {
60
- counts[s.tool] = (counts[s.tool] || 0) + 1;
61
- }
62
- if ((counts['ppt_build_layout_a'] || 0) > 3) {
63
- return `Layout A count ${counts['ppt_build_layout_a']} exceeds max 3`;
64
- }
65
- if ((counts['ppt_build_layout_b'] || 0) > 4) {
66
- return `Layout B count ${counts['ppt_build_layout_b']} exceeds max 4`;
67
- }
68
- if ((counts['ppt_build_layout_d'] || 0) > 3) {
69
- return `Layout D count ${counts['ppt_build_layout_d']} exceeds max 3`;
70
- }
71
- const layoutTypes = new Set(plan.slides
72
- .map(s => s.tool)
73
- .filter(t => t.startsWith('ppt_build_layout_')));
74
- if (layoutTypes.size < 5) {
75
- return `Only ${layoutTypes.size} layout types used, need at least 5`;
76
- }
77
51
  return null;
78
52
  }
79
53
  function parseJsonPlan(raw) {
80
54
  let cleaned = raw.trim();
81
55
  if (cleaned.startsWith('```')) {
82
- cleaned = cleaned.replace(/^```(?:json)?\s*\n?/, '').replace(/\n?```\s*$/, '');
56
+ cleaned = cleaned.replace(/^```(?:json|JSON)?\s*\n?/, '').replace(/\n?```\s*$/, '');
57
+ }
58
+ const firstBrace = cleaned.indexOf('{');
59
+ if (firstBrace > 0) {
60
+ cleaned = cleaned.slice(firstBrace);
61
+ }
62
+ const lastBrace = cleaned.lastIndexOf('}');
63
+ if (lastBrace >= 0 && lastBrace < cleaned.length - 1) {
64
+ cleaned = cleaned.slice(0, lastBrace + 1);
83
65
  }
84
66
  try {
85
67
  return JSON.parse(cleaned);
86
68
  }
87
- catch {
69
+ catch (e) {
70
+ logger.debug('parseJsonPlan: first parse attempt failed', { error: String(e), length: cleaned.length });
88
71
  const match = cleaned.match(/\{[\s\S]*\}/);
89
72
  if (match) {
90
73
  try {
91
74
  return JSON.parse(match[0]);
92
75
  }
93
76
  catch {
94
- return null;
77
+ try {
78
+ let repaired = match[0];
79
+ let braces = 0, brackets = 0;
80
+ let inString = false, escape = false;
81
+ for (const ch of repaired) {
82
+ if (escape) {
83
+ escape = false;
84
+ continue;
85
+ }
86
+ if (ch === '\\') {
87
+ escape = true;
88
+ continue;
89
+ }
90
+ if (ch === '"') {
91
+ inString = !inString;
92
+ continue;
93
+ }
94
+ if (inString)
95
+ continue;
96
+ if (ch === '{')
97
+ braces++;
98
+ else if (ch === '}')
99
+ braces--;
100
+ else if (ch === '[')
101
+ brackets++;
102
+ else if (ch === ']')
103
+ brackets--;
104
+ }
105
+ if (inString)
106
+ repaired += '"';
107
+ for (let i = 0; i < brackets; i++)
108
+ repaired += ']';
109
+ for (let i = 0; i < braces; i++)
110
+ repaired += '}';
111
+ return JSON.parse(repaired);
112
+ }
113
+ catch {
114
+ return null;
115
+ }
95
116
  }
96
117
  }
97
118
  return null;
98
119
  }
99
120
  }
121
+ function extractHtml(raw) {
122
+ const trimmed = raw.trim();
123
+ if (trimmed.startsWith('<!DOCTYPE') || trimmed.startsWith('<html')) {
124
+ return trimmed;
125
+ }
126
+ const fenceMatch = trimmed.match(/```(?:html)?\s*\n([\s\S]*?)\n```/);
127
+ if (fenceMatch?.[1]) {
128
+ return fenceMatch[1].trim();
129
+ }
130
+ const docMatch = trimmed.match(/(<!DOCTYPE[\s\S]*<\/html>)/i);
131
+ if (docMatch?.[1]) {
132
+ return docMatch[1].trim();
133
+ }
134
+ return null;
135
+ }
136
+ function getTempDir() {
137
+ const platform = getPlatform();
138
+ if (platform === 'wsl') {
139
+ return { writePath: '/mnt/c/temp', winPath: 'C:\\temp' };
140
+ }
141
+ return { writePath: 'C:\\temp', winPath: 'C:\\temp' };
142
+ }
143
+ function ensureTempDir(writePath) {
144
+ if (!fs.existsSync(writePath)) {
145
+ fs.mkdirSync(writePath, { recursive: true });
146
+ }
147
+ }
148
+ function normalizeDesign(raw) {
149
+ return {
150
+ primary_color: raw['primary_color'] || DEFAULT_DESIGN.primary_color,
151
+ accent_color: raw['accent_color'] || DEFAULT_DESIGN.accent_color,
152
+ background_color: raw['background_color'] || DEFAULT_DESIGN.background_color,
153
+ text_color: raw['text_color'] || DEFAULT_DESIGN.text_color,
154
+ accent_light: raw['accent_light'] || DEFAULT_DESIGN.accent_light,
155
+ gradient_end: raw['gradient_end'] || DEFAULT_DESIGN.gradient_end,
156
+ font_title: raw['font_title'] || DEFAULT_DESIGN.font_title,
157
+ font_body: raw['font_body'] || DEFAULT_DESIGN.font_body,
158
+ mood: raw['mood'] || DEFAULT_DESIGN.mood,
159
+ design_notes: raw['design_notes'] || DEFAULT_DESIGN.design_notes,
160
+ };
161
+ }
100
162
  async function runStructured(llmClient, instruction) {
101
163
  const startTime = Date.now();
102
164
  const phaseLogger = getSubAgentPhaseLogger();
103
165
  const toolCallLogger = getSubAgentToolCallLogger();
104
166
  let totalToolCalls = 0;
167
+ const timestamp = Date.now();
105
168
  logger.enter('PPT-Create.runStructured');
106
169
  const hasKorean = /[\uac00-\ud7af\u1100-\u11ff]/.test(instruction);
107
170
  const language = hasKorean ? 'ko' : 'en';
@@ -115,7 +178,7 @@ async function runStructured(llmClient, instruction) {
115
178
  { role: 'user', content: instruction },
116
179
  ],
117
180
  temperature: 0.5,
118
- max_tokens: 800,
181
+ max_tokens: 1200,
119
182
  });
120
183
  const enhMsg = enhRes.choices[0]?.message;
121
184
  guidance = enhMsg ? extractContent(enhMsg) : '';
@@ -138,18 +201,49 @@ async function runStructured(llmClient, instruction) {
138
201
  { role: 'user', content: enhancedInstruction },
139
202
  ],
140
203
  temperature: 0.4,
141
- max_tokens: 2000,
204
+ max_tokens: 8000,
142
205
  });
143
206
  const planMsg = planRes.choices[0]?.message;
207
+ const finishReason = planRes.choices[0]?.finish_reason;
144
208
  const rawPlan = planMsg ? extractContent(planMsg) : '';
209
+ if (finishReason === 'length') {
210
+ logger.warn('PPT planning response was truncated (finish_reason=length)');
211
+ }
212
+ logger.debug('PPT planning raw response', { length: rawPlan.length, finishReason, first200: rawPlan.slice(0, 200) });
145
213
  plan = rawPlan ? parseJsonPlan(rawPlan) : null;
146
214
  if (plan) {
215
+ plan.design = normalizeDesign(plan.design);
147
216
  const validationError = validatePlan(plan);
148
217
  if (validationError) {
149
218
  logger.warn('PPT plan validation failed', { error: validationError });
150
219
  if (phaseLogger)
151
- phaseLogger('powerpoint-create', 'planning', `Validation failed: ${validationError}. Falling back.`);
152
- plan = null;
220
+ phaseLogger('powerpoint-create', 'planning', `Validation failed: ${validationError}. Retrying...`);
221
+ const retryRes = await llmClient.chatCompletion({
222
+ messages: [
223
+ { role: 'system', content: PPT_STRUCTURED_PLANNING_PROMPT },
224
+ { role: 'user', content: enhancedInstruction },
225
+ ],
226
+ temperature: 0.2,
227
+ max_tokens: 8000,
228
+ });
229
+ const retryMsg = retryRes.choices[0]?.message;
230
+ const retryRaw = retryMsg ? extractContent(retryMsg) : '';
231
+ const retryPlan = retryRaw ? parseJsonPlan(retryRaw) : null;
232
+ if (retryPlan) {
233
+ retryPlan.design = normalizeDesign(retryPlan.design);
234
+ const retryError = validatePlan(retryPlan);
235
+ if (!retryError) {
236
+ plan = retryPlan;
237
+ if (phaseLogger)
238
+ phaseLogger('powerpoint-create', 'planning', `Retry succeeded (${plan.slides.length} slides)`);
239
+ }
240
+ else {
241
+ plan = null;
242
+ }
243
+ }
244
+ else {
245
+ plan = null;
246
+ }
153
247
  }
154
248
  else {
155
249
  if (phaseLogger)
@@ -182,147 +276,171 @@ async function runStructured(llmClient, instruction) {
182
276
  return agent.run(instruction);
183
277
  }
184
278
  if (phaseLogger)
185
- phaseLogger('powerpoint-create', 'execution', 'Starting slide-by-slide execution...');
186
- const toolMap = new Map();
187
- for (const tool of POWERPOINT_CREATE_TOOLS) {
188
- toolMap.set(tool.definition.function.name, tool);
189
- }
279
+ phaseLogger('powerpoint-create', 'execution', 'Starting HTML rendering pipeline...');
280
+ const { writePath: tempWritePath, winPath: tempWinPath } = getTempDir();
281
+ ensureTempDir(tempWritePath);
190
282
  const pathMatch = instruction.match(/([A-Za-z]:\\[^\s,]+\.pptx|\/[^\s,]+\.pptx)/i);
191
283
  const savePath = pathMatch ? pathMatch[1] : undefined;
192
- const createTool = toolMap.get('powerpoint_create');
193
- if (!createTool) {
194
- return { success: false, error: 'powerpoint_create tool not found' };
195
- }
196
- resetLayoutCounters();
197
- const createResult = await createTool.execute({ reason: 'Creating new presentation' });
284
+ const createResult = await powerpointClient.powerpointCreate();
198
285
  totalToolCalls++;
199
286
  if (toolCallLogger)
200
- toolCallLogger('powerpoint-create', 'powerpoint_create', { reason: 'Creating new presentation' }, createResult.result || createResult.error || '', createResult.success, 0, totalToolCalls);
287
+ toolCallLogger('powerpoint-create', 'powerpoint_create', {}, createResult.success ? 'Created' : createResult['error'] || '', createResult.success, 0, totalToolCalls);
201
288
  if (!createResult.success) {
202
- return { success: false, error: `Failed to create presentation: ${createResult.error}` };
289
+ return { success: false, error: `Failed to create presentation: ${createResult['error']}` };
290
+ }
291
+ const titleSlide = plan.slides.find(s => s.type === 'title');
292
+ if (titleSlide) {
293
+ const kstNow = new Date(Date.now() + 9 * 60 * 60 * 1000);
294
+ const kstDate = `${kstNow.getUTCFullYear()}년 ${kstNow.getUTCMonth() + 1}월`;
295
+ titleSlide.content_direction = (titleSlide.content_direction || '') + `\nDate text to display: ${kstDate}`;
203
296
  }
204
297
  const builtSlides = [];
298
+ let failCount = 0;
299
+ const tempFiles = [];
205
300
  for (let i = 0; i < plan.slides.length; i++) {
206
301
  const slidePlan = plan.slides[i];
207
302
  const slideNum = i + 1;
208
- const tool = toolMap.get(slidePlan.tool);
209
- if (!tool) {
210
- logger.warn(`Tool ${slidePlan.tool} not found, skipping slide ${slideNum}`);
211
- continue;
303
+ if (failCount >= 3) {
304
+ logger.warn('Too many slide failures, stopping');
305
+ break;
212
306
  }
213
- if (slidePlan.tool === 'ppt_build_title_slide') {
214
- const kstNow = new Date(Date.now() + 9 * 60 * 60 * 1000);
215
- const kstDate = `${kstNow.getUTCFullYear()}년 ${kstNow.getUTCMonth() + 1}월`;
216
- const args = {
217
- title: slidePlan.title,
218
- subtitle: slidePlan.params?.['subtitle'] || '',
219
- date_text: kstDate,
220
- color_scheme: plan.design.color_scheme,
221
- design_style: plan.design.design_style,
222
- };
223
- const result = await tool.execute(args);
224
- totalToolCalls++;
225
- if (toolCallLogger)
226
- toolCallLogger('powerpoint-create', slidePlan.tool, args, result.result || result.error || '', result.success, slideNum, totalToolCalls);
227
- if (result.success) {
228
- builtSlides.push(`Slide ${slideNum}: ${slidePlan.title} (title)`);
229
- }
230
- else {
231
- logger.warn(`Title slide failed: ${result.error}`);
307
+ if (phaseLogger)
308
+ phaseLogger('powerpoint-create', 'execution', `Rendering slide ${slideNum}/${plan.slides.length}: ${slidePlan.title}`);
309
+ const htmlPrompt = buildSlideHtmlPrompt(slidePlan.title, slidePlan.content_direction || '', plan.design, i, plan.slides.length, language);
310
+ let html = null;
311
+ try {
312
+ const htmlRes = await llmClient.chatCompletion({
313
+ messages: [
314
+ { role: 'system', content: htmlPrompt },
315
+ { role: 'user', content: 'Generate the HTML slide now.' },
316
+ ],
317
+ temperature: 0.3,
318
+ max_tokens: 8000,
319
+ });
320
+ const htmlMsg = htmlRes.choices[0]?.message;
321
+ const rawHtml = htmlMsg ? extractContent(htmlMsg) : '';
322
+ html = extractHtml(rawHtml);
323
+ if (!html && rawHtml.length > 100) {
324
+ logger.warn(`Slide ${slideNum}: HTML extraction failed, retrying`);
325
+ const retryRes = await llmClient.chatCompletion({
326
+ messages: [
327
+ { role: 'system', content: htmlPrompt },
328
+ { role: 'user', content: 'Generate the complete HTML document. Start with <!DOCTYPE html> and end with </html>. No markdown fences.' },
329
+ ],
330
+ temperature: 0.2,
331
+ max_tokens: 4000,
332
+ });
333
+ const retryMsg = retryRes.choices[0]?.message;
334
+ const retryRaw = retryMsg ? extractContent(retryMsg) : '';
335
+ html = extractHtml(retryRaw);
232
336
  }
233
- continue;
234
337
  }
235
- if (slidePlan.tool === 'ppt_build_closing_slide') {
236
- const closingText = slidePlan.params?.['text'] || slidePlan.title || '감사합니다';
237
- const args = {
238
- text: closingText,
239
- subtitle: slidePlan.params?.['subtitle'] || '',
240
- color_scheme: plan.design.color_scheme,
241
- design_style: plan.design.design_style,
242
- };
243
- const result = await tool.execute(args);
244
- totalToolCalls++;
245
- if (toolCallLogger)
246
- toolCallLogger('powerpoint-create', slidePlan.tool, args, result.result || result.error || '', result.success, slideNum, totalToolCalls);
247
- if (result.success) {
248
- builtSlides.push(`Slide ${slideNum}: ${closingText} (closing)`);
249
- }
250
- else {
251
- logger.warn(`Closing slide failed: ${result.error}`);
252
- }
338
+ catch (e) {
339
+ logger.warn(`Slide ${slideNum}: LLM call failed: ${e}`);
340
+ }
341
+ if (!html) {
342
+ logger.warn(`Slide ${slideNum}: Failed to generate HTML, skipping`);
343
+ failCount++;
253
344
  continue;
254
345
  }
255
- const slideSystemPrompt = buildSlideSystemPrompt(slidePlan.tool, slidePlan.title, slidePlan.direction || '', guidance, language);
256
- if (!slideSystemPrompt) {
257
- logger.warn(`No system prompt for ${slidePlan.tool}, skipping`);
346
+ const htmlFileName = `hanseol_slide_${slideNum}_${timestamp}.html`;
347
+ const pngFileName = `hanseol_slide_${slideNum}_${timestamp}.png`;
348
+ const htmlWritePath = path.join(tempWritePath, htmlFileName);
349
+ const pngWritePath = path.join(tempWritePath, pngFileName);
350
+ const htmlWinPath = `${tempWinPath}\\${htmlFileName}`;
351
+ const pngWinPath = `${tempWinPath}\\${pngFileName}`;
352
+ try {
353
+ fs.writeFileSync(htmlWritePath, html, 'utf-8');
354
+ tempFiles.push(htmlWritePath);
355
+ }
356
+ catch (e) {
357
+ logger.warn(`Slide ${slideNum}: Failed to write HTML file: ${e}`);
358
+ failCount++;
258
359
  continue;
259
360
  }
361
+ let renderSuccess = false;
260
362
  try {
261
- const slideRes = await llmClient.chatCompletion({
262
- messages: [
263
- { role: 'system', content: slideSystemPrompt },
264
- { role: 'user', content: `Build slide "${slidePlan.title}" now. Call the tool with all required parameters.` },
265
- ],
266
- tools: [tool.definition],
267
- temperature: 0.3,
268
- max_tokens: 2000,
269
- });
270
- const msg = slideRes.choices[0]?.message;
271
- if (!msg?.tool_calls || msg.tool_calls.length === 0) {
272
- logger.warn(`Slide ${slideNum}: LLM did not call tool, skipping`);
273
- continue;
363
+ const renderResult = await powerpointClient.renderHtmlToImage(htmlWinPath, pngWinPath);
364
+ totalToolCalls++;
365
+ renderSuccess = renderResult.success;
366
+ if (!renderSuccess) {
367
+ await new Promise(r => setTimeout(r, 2000));
368
+ const retryRender = await powerpointClient.renderHtmlToImage(htmlWinPath, pngWinPath);
369
+ totalToolCalls++;
370
+ renderSuccess = retryRender.success;
274
371
  }
275
- const tc = msg.tool_calls[0];
276
- let args;
372
+ }
373
+ catch (e) {
374
+ logger.warn(`Slide ${slideNum}: Edge screenshot failed: ${e}`);
277
375
  try {
278
- args = JSON.parse(tc.function.arguments);
376
+ await new Promise(r => setTimeout(r, 2000));
377
+ const retryRender = await powerpointClient.renderHtmlToImage(htmlWinPath, pngWinPath);
378
+ totalToolCalls++;
379
+ renderSuccess = retryRender.success;
279
380
  }
280
381
  catch {
281
- logger.warn(`Slide ${slideNum}: Invalid JSON args from LLM`);
282
- continue;
283
- }
284
- args['color_scheme'] = plan.design.color_scheme;
285
- args['design_style'] = plan.design.design_style;
286
- if (slidePlan.tool === 'ppt_build_layout_f' && Array.isArray(args['table_data'])) {
287
- args['table_data'] = args['table_data'].filter(row => Array.isArray(row));
288
- }
289
- const result = await tool.execute(args);
290
- totalToolCalls++;
291
- if (toolCallLogger)
292
- toolCallLogger('powerpoint-create', tc.function.name, args, result.result || result.error || '', result.success, slideNum, totalToolCalls);
293
- if (result.success) {
294
- builtSlides.push(`Slide ${slideNum}: ${slidePlan.title} (${slidePlan.tool.replace('ppt_build_layout_', 'Layout ').toUpperCase()})`);
295
- }
296
- else {
297
- logger.warn(`Slide ${slideNum} failed: ${result.error}`);
382
+ renderSuccess = false;
298
383
  }
299
384
  }
300
- catch (e) {
301
- logger.warn(`Slide ${slideNum} LLM call failed: ${e}`);
385
+ try {
386
+ fs.unlinkSync(htmlWritePath);
387
+ }
388
+ catch { }
389
+ if (!renderSuccess) {
390
+ logger.warn(`Slide ${slideNum}: Screenshot rendering failed, skipping`);
391
+ failCount++;
392
+ continue;
393
+ }
394
+ tempFiles.push(pngWritePath);
395
+ const addResult = await powerpointClient.powerpointAddSlide(7);
396
+ totalToolCalls++;
397
+ if (!addResult.success) {
398
+ logger.warn(`Slide ${slideNum}: Failed to add blank slide`);
399
+ failCount++;
400
+ continue;
401
+ }
402
+ const bgResult = await powerpointClient.powerpointSetBackground(slideNum, { imagePath: pngWinPath });
403
+ totalToolCalls++;
404
+ if (toolCallLogger)
405
+ toolCallLogger('powerpoint-create', 'setBackground', { slideNum, imagePath: pngWinPath }, bgResult.success ? 'OK' : 'Failed', bgResult.success, slideNum, totalToolCalls);
406
+ if (bgResult.success) {
407
+ builtSlides.push(`Slide ${slideNum}: ${slidePlan.title} (${slidePlan.type})`);
408
+ }
409
+ else {
410
+ logger.warn(`Slide ${slideNum}: Failed to set background: ${JSON.stringify(bgResult)}`);
411
+ failCount++;
302
412
  }
303
413
  }
304
- const saveTool = toolMap.get('powerpoint_save');
305
- if (saveTool) {
306
- const saveArgs = { reason: 'Saving completed presentation' };
414
+ if (builtSlides.length > 0) {
415
+ const saveArgs = {};
307
416
  if (savePath)
308
417
  saveArgs['path'] = savePath;
309
- const saveResult = await saveTool.execute(saveArgs);
310
- totalToolCalls++;
311
- if (toolCallLogger)
312
- toolCallLogger('powerpoint-create', 'powerpoint_save', saveArgs, saveResult.result || saveResult.error || '', saveResult.success, 0, totalToolCalls);
313
- if (!saveResult.success) {
314
- const fallbackArgs = { reason: 'Retry save', path: 'C:\\temp\\presentation.pptx' };
315
- const retryResult = await saveTool.execute(fallbackArgs);
418
+ const saveTool = POWERPOINT_CREATE_TOOLS.find(t => t.definition.function.name === 'powerpoint_save');
419
+ if (saveTool) {
420
+ const saveResult = await saveTool.execute(saveArgs);
316
421
  totalToolCalls++;
317
422
  if (toolCallLogger)
318
- toolCallLogger('powerpoint-create', 'powerpoint_save', fallbackArgs, retryResult.result || retryResult.error || '', retryResult.success, 0, totalToolCalls);
423
+ toolCallLogger('powerpoint-create', 'powerpoint_save', saveArgs, saveResult.result || saveResult.error || '', saveResult.success, 0, totalToolCalls);
424
+ if (!saveResult.success) {
425
+ const fallbackArgs = { path: 'C:\\temp\\presentation.pptx' };
426
+ const retryResult = await saveTool.execute(fallbackArgs);
427
+ totalToolCalls++;
428
+ if (toolCallLogger)
429
+ toolCallLogger('powerpoint-create', 'powerpoint_save', fallbackArgs, retryResult.result || retryResult.error || '', retryResult.success, 0, totalToolCalls);
430
+ }
431
+ }
432
+ }
433
+ for (const tempFile of tempFiles) {
434
+ try {
435
+ fs.unlinkSync(tempFile);
319
436
  }
437
+ catch { }
320
438
  }
321
439
  const duration = Date.now() - startTime;
322
- const summary = `Presentation created with ${builtSlides.length} slides (${plan.design.color_scheme}, ${plan.design.design_style} style):\n${builtSlides.join('\n')}`;
440
+ const summary = `Presentation created with ${builtSlides.length} slides (HTML rendering, ${plan.design.mood}):\n${builtSlides.join('\n')}`;
323
441
  logger.exit('PPT-Create.runStructured', { slideCount: builtSlides.length, totalToolCalls, duration });
324
442
  return {
325
- success: true,
443
+ success: builtSlides.length > 0,
326
444
  result: summary,
327
445
  metadata: { iterations: plan.slides.length, toolCalls: totalToolCalls, duration },
328
446
  };
@@ -333,7 +451,7 @@ export function createPowerPointCreateRequestTool() {
333
451
  type: 'function',
334
452
  function: {
335
453
  name: 'powerpoint_create_agent',
336
- description: 'Autonomous PowerPoint CREATION agent. Creates NEW presentations from scratch with professional slide designs, color schemes, and visual hierarchy. Uses high-level builder tools where each tool call creates one complete slide. Give it a topic or outline and it produces a polished, enterprise-grade presentation. For EDITING existing .pptx files, use powerpoint_modify_agent instead.',
454
+ description: 'Autonomous PowerPoint CREATION agent. Creates NEW presentations from scratch with professional slide designs, color schemes, and visual hierarchy. Uses HTML rendering pipeline for maximum design quality — each slide is rendered as a beautiful HTML page and captured as a high-quality image. Give it a topic or outline and it produces a polished, enterprise-grade presentation. For EDITING existing .pptx files, use powerpoint_modify_agent instead.',
337
455
  parameters: {
338
456
  type: 'object',
339
457
  properties: {