hanseol-dev 5.0.3-dev.3 → 5.0.3-dev.31
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/agents/office/powerpoint-create-agent.d.ts.map +1 -1
- package/dist/agents/office/powerpoint-create-agent.js +273 -299
- package/dist/agents/office/powerpoint-create-agent.js.map +1 -1
- package/dist/agents/office/powerpoint-create-prompts.d.ts +3 -2
- package/dist/agents/office/powerpoint-create-prompts.d.ts.map +1 -1
- package/dist/agents/office/powerpoint-create-prompts.js +300 -244
- package/dist/agents/office/powerpoint-create-prompts.js.map +1 -1
- package/dist/agents/office/word-create-agent.js +4 -4
- package/dist/agents/office/word-create-prompts.d.ts +3 -3
- package/dist/agents/office/word-create-prompts.d.ts.map +1 -1
- package/dist/agents/office/word-create-prompts.js +29 -18
- package/dist/agents/office/word-create-prompts.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/tools/office/powerpoint-client.d.ts.map +1 -1
- package/dist/tools/office/powerpoint-client.js +4 -0
- package/dist/tools/office/powerpoint-client.js.map +1 -1
- package/dist/tools/office/word-client.d.ts +2 -0
- package/dist/tools/office/word-client.d.ts.map +1 -1
- package/dist/tools/office/word-client.js +67 -5
- package/dist/tools/office/word-client.js.map +1 -1
- package/dist/tools/office/word-tools/section-builders.d.ts.map +1 -1
- package/dist/tools/office/word-tools/section-builders.js +32 -26
- package/dist/tools/office/word-tools/section-builders.js.map +1 -1
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ import { powerpointClient } from '../../tools/office/powerpoint-client.js';
|
|
|
4
4
|
import { getSubAgentPhaseLogger, getSubAgentToolCallLogger } from '../common/sub-agent.js';
|
|
5
5
|
import { logger } from '../../utils/logger.js';
|
|
6
6
|
import { getPlatform } from '../../utils/platform-utils.js';
|
|
7
|
-
import { PPT_DESIGN_PROMPT,
|
|
7
|
+
import { PPT_DESIGN_PROMPT, validateSlideHtml, buildFreeHtmlPrompt, } from './powerpoint-create-prompts.js';
|
|
8
8
|
const DEFAULT_DESIGN = {
|
|
9
9
|
primary_color: '#1B2A4A',
|
|
10
10
|
accent_color: '#00D4AA',
|
|
@@ -228,18 +228,6 @@ function parseJsonPlan(raw) {
|
|
|
228
228
|
return null;
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
|
-
function extractHtml(raw) {
|
|
232
|
-
const trimmed = raw.trim();
|
|
233
|
-
if (trimmed.startsWith('<!DOCTYPE') || trimmed.startsWith('<html'))
|
|
234
|
-
return trimmed;
|
|
235
|
-
const fenceMatch = trimmed.match(/```(?:html)?\s*\n([\s\S]*?)\n```/);
|
|
236
|
-
if (fenceMatch?.[1])
|
|
237
|
-
return fenceMatch[1].trim();
|
|
238
|
-
const docMatch = trimmed.match(/(<!DOCTYPE[\s\S]*<\/html>)/i);
|
|
239
|
-
if (docMatch?.[1])
|
|
240
|
-
return docMatch[1].trim();
|
|
241
|
-
return null;
|
|
242
|
-
}
|
|
243
231
|
function injectEdgeSizing(html, backgroundColor) {
|
|
244
232
|
let result = html.replace(/<meta\s+name=["']viewport["'][^>]*>/gi, '');
|
|
245
233
|
const bgColor = backgroundColor || '#000000';
|
|
@@ -258,50 +246,6 @@ function injectEdgeSizing(html, backgroundColor) {
|
|
|
258
246
|
}
|
|
259
247
|
return result;
|
|
260
248
|
}
|
|
261
|
-
function injectViewportCss(html, backgroundColor) {
|
|
262
|
-
let result = html.replace(/<meta\s+name=["']viewport["'][^>]*>/gi, '');
|
|
263
|
-
const bgColor = backgroundColor || '#000000';
|
|
264
|
-
const overrideCss = `<style>*{box-sizing:border-box;word-break:keep-all;overflow-wrap:break-word}html{width:2040px!important;height:1200px!important;overflow:hidden!important;margin:0!important;background-color:${bgColor}!important;zoom:1!important}body{width:1920px!important;height:1080px!important;min-width:1920px!important;min-height:1080px!important;overflow:hidden!important;margin:0!important;display:flex!important;flex-direction:column!important;zoom:1!important;font-size:26px!important}body>div,body>main,body>section,body>article,body>header,body>footer{max-width:none!important;zoom:1!important}body>*>div,body>*>main,body>*>section,body>*>article{max-width:none!important;zoom:1!important}body>*:only-child{display:flex!important;flex-direction:column!important;flex:1!important;min-height:1080px!important}body>*:only-child>*:nth-child(2),body>*:only-child>*:nth-child(3),body>*:only-child>*:last-child:not(:first-child){flex:1!important;display:flex!important;align-items:stretch!important}body>.content,body>*:nth-child(2),body>*:nth-child(3),body *>.content,body [class*=content]{flex:1!important;align-items:stretch!important;align-content:stretch!important;grid-auto-rows:1fr!important}[class*=card],[class*=item],[class*=step],[class*=milestone],[class*=feature]{justify-content:flex-start!important;gap:20px!important}</style>`;
|
|
265
|
-
if (result.includes('</head>')) {
|
|
266
|
-
result = result.replace('</head>', `${overrideCss}</head>`);
|
|
267
|
-
}
|
|
268
|
-
else if (result.includes('<head>')) {
|
|
269
|
-
result = result.replace('<head>', `<head>${overrideCss}`);
|
|
270
|
-
}
|
|
271
|
-
else if (result.includes('<html')) {
|
|
272
|
-
result = result.replace(/<html[^>]*>/, (m) => `${m}<head>${overrideCss}</head>`);
|
|
273
|
-
}
|
|
274
|
-
else {
|
|
275
|
-
result = overrideCss + result;
|
|
276
|
-
}
|
|
277
|
-
return result;
|
|
278
|
-
}
|
|
279
|
-
function injectTitleContrastFix(html, designTextColor) {
|
|
280
|
-
const safeColor = designTextColor.replace(/'/g, "\\'");
|
|
281
|
-
const script = `<script>(function(){` +
|
|
282
|
-
`var dc='${safeColor}';` +
|
|
283
|
-
`function lum(c){var m=c.match(/[\\d.]+/g);if(!m||m.length<3)return -1;` +
|
|
284
|
-
`var s=[m[0]/255,m[1]/255,m[2]/255].map(function(v){return v<=0.03928?v/12.92:Math.pow((v+0.055)/1.055,2.4)});` +
|
|
285
|
-
`return 0.2126*s[0]+0.7152*s[1]+0.0722*s[2]}` +
|
|
286
|
-
`function getBg(el){while(el){var s=getComputedStyle(el);var bg=s.backgroundColor;` +
|
|
287
|
-
`var m=bg.match(/[\\d.]+/g);if(m&&m.length>=3){if(m.length<4||parseFloat(m[3])>0.1)return bg}` +
|
|
288
|
-
`el=el.parentElement}return'rgb(255,255,255)'}` +
|
|
289
|
-
`var els=document.querySelectorAll('h1,h2');` +
|
|
290
|
-
`for(var i=0;i<els.length;i++){var el=els[i];var cs=getComputedStyle(el);` +
|
|
291
|
-
`var tfc=cs.webkitTextFillColor||'';` +
|
|
292
|
-
`if(tfc==='transparent'||tfc==='rgba(0, 0, 0, 0)'){` +
|
|
293
|
-
`el.style.setProperty('-webkit-text-fill-color','initial','important')}` +
|
|
294
|
-
`if(parseFloat(cs.opacity)<0.6){el.style.setProperty('opacity','1','important')}` +
|
|
295
|
-
`var fg=cs.color;var bg=getBg(el);var fl=lum(fg),bl=lum(bg);` +
|
|
296
|
-
`if(fl>=0&&bl>=0){var r=(Math.max(fl,bl)+0.05)/(Math.min(fl,bl)+0.05);` +
|
|
297
|
-
`if(r<3){el.style.setProperty('color',dc,'important');` +
|
|
298
|
-
`el.style.setProperty('-webkit-text-fill-color',dc,'important')}}}` +
|
|
299
|
-
`})()<\/script>`;
|
|
300
|
-
if (html.includes('</body>')) {
|
|
301
|
-
return html.replace('</body>', `${script}</body>`);
|
|
302
|
-
}
|
|
303
|
-
return html + script;
|
|
304
|
-
}
|
|
305
249
|
function escapeHtml(text) {
|
|
306
250
|
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
307
251
|
}
|
|
@@ -459,48 +403,38 @@ body::after {
|
|
|
459
403
|
</body>
|
|
460
404
|
</html>`;
|
|
461
405
|
}
|
|
462
|
-
function
|
|
463
|
-
if (slideIndex !== 1)
|
|
464
|
-
return false;
|
|
465
|
-
return /개요|목차|overview|agenda|outline|순서|발표\s*구성|contents|목록/i.test(title);
|
|
466
|
-
}
|
|
467
|
-
function parseOverviewItems(contentDirection) {
|
|
406
|
+
function buildFallbackSlideHtml(design, title, contentDirection, slideNum) {
|
|
468
407
|
const items = [];
|
|
469
|
-
const
|
|
470
|
-
for (const
|
|
471
|
-
const
|
|
472
|
-
if (
|
|
473
|
-
|
|
408
|
+
const parts = contentDirection.split(/\(\d+\)\s*|•\s*|\n-\s*|\n\d+[.)]\s*|Step\s*\d+[.:]\s*/i);
|
|
409
|
+
for (const part of parts) {
|
|
410
|
+
const cleaned = part.replace(/Layout:.*$/i, '').trim();
|
|
411
|
+
if (cleaned.length > 5) {
|
|
412
|
+
const sentence = cleaned.split(/[.。!]\s/)[0] || cleaned;
|
|
413
|
+
items.push(sentence.slice(0, 80));
|
|
474
414
|
}
|
|
475
415
|
}
|
|
476
|
-
if (items.length
|
|
477
|
-
const
|
|
478
|
-
for (const part of
|
|
479
|
-
const
|
|
480
|
-
if (
|
|
481
|
-
items.push(
|
|
416
|
+
if (items.length < 3) {
|
|
417
|
+
const commaParts = contentDirection.split(/[,;,;]\s*/);
|
|
418
|
+
for (const part of commaParts) {
|
|
419
|
+
const cleaned = part.replace(/Layout:.*$/i, '').trim();
|
|
420
|
+
if (cleaned.length > 8 && !items.includes(cleaned.slice(0, 80))) {
|
|
421
|
+
items.push(cleaned.slice(0, 80));
|
|
482
422
|
}
|
|
483
423
|
}
|
|
484
424
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
const topRow = itemCount <= 3 ? items : items.slice(0, Math.ceil(itemCount / 2));
|
|
494
|
-
const bottomRow = itemCount <= 3 ? [] : items.slice(Math.ceil(itemCount / 2));
|
|
495
|
-
function renderCard(item, idx) {
|
|
496
|
-
const color = badgeColors[idx % badgeColors.length];
|
|
497
|
-
return `
|
|
498
|
-
<div class="card">
|
|
499
|
-
<div class="badge" style="background:${color}">${idx + 1}</div>
|
|
500
|
-
<div class="card-title">${escapeHtml(item.title)}</div>
|
|
501
|
-
${item.desc ? `<div class="card-desc">${escapeHtml(item.desc)}</div>` : ''}
|
|
502
|
-
</div>`;
|
|
425
|
+
if (items.length < 3) {
|
|
426
|
+
const sentences = contentDirection.split(/[.。!?]\s+/);
|
|
427
|
+
for (const s of sentences) {
|
|
428
|
+
const cleaned = s.replace(/Layout:.*$/i, '').trim();
|
|
429
|
+
if (cleaned.length > 10 && !items.some(i => i.startsWith(cleaned.slice(0, 20)))) {
|
|
430
|
+
items.push(cleaned.slice(0, 80));
|
|
431
|
+
}
|
|
432
|
+
}
|
|
503
433
|
}
|
|
434
|
+
const bulletItems = items.slice(0, 6);
|
|
435
|
+
const useGrid = bulletItems.length >= 4;
|
|
436
|
+
const gridCols = useGrid ? 'grid-template-columns:1fr 1fr' : 'grid-template-columns:1fr';
|
|
437
|
+
const pointsHtml = bulletItems.map((item, i) => `<div class="point"><div class="point-num">${i + 1}</div><div class="point-text">${escapeHtml(item)}</div></div>`).join('\n ');
|
|
504
438
|
return `<!DOCTYPE html>
|
|
505
439
|
<html lang="ko">
|
|
506
440
|
<head>
|
|
@@ -510,82 +444,30 @@ function buildOverviewSlideHtml(design, title, subtitle, items, slideNum) {
|
|
|
510
444
|
html, body { width: 1920px; height: 1080px; overflow: hidden; }
|
|
511
445
|
body {
|
|
512
446
|
background: ${design.background_color};
|
|
513
|
-
font-family: "${design.font_body}", "
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
.header {
|
|
518
|
-
background: linear-gradient(135deg, ${design.primary_color}, ${design.gradient_end});
|
|
519
|
-
padding: 48px 80px 40px;
|
|
520
|
-
flex-shrink: 0;
|
|
521
|
-
}
|
|
522
|
-
.header-title {
|
|
523
|
-
font-size: 52px; font-weight: 800; color: #ffffff;
|
|
524
|
-
font-family: "${design.font_title}", "Segoe UI", sans-serif;
|
|
525
|
-
margin-bottom: 8px;
|
|
526
|
-
}
|
|
527
|
-
.header-subtitle {
|
|
528
|
-
font-size: 24px; font-weight: 400; color: rgba(255,255,255,0.75);
|
|
529
|
-
}
|
|
530
|
-
.content {
|
|
531
|
-
flex: 1; display: flex; flex-direction: column;
|
|
532
|
-
padding: 48px 80px 40px;
|
|
533
|
-
gap: 24px;
|
|
534
|
-
justify-content: center;
|
|
535
|
-
}
|
|
536
|
-
.row {
|
|
537
|
-
display: flex; gap: 24px;
|
|
538
|
-
justify-content: center;
|
|
539
|
-
}
|
|
540
|
-
.card {
|
|
541
|
-
flex: 1;
|
|
542
|
-
max-width: 340px;
|
|
543
|
-
background: #ffffff;
|
|
544
|
-
border-radius: 16px;
|
|
545
|
-
padding: 36px 32px;
|
|
546
|
-
box-shadow: 0 4px 20px rgba(0,0,0,0.06);
|
|
547
|
-
display: flex; flex-direction: column;
|
|
548
|
-
align-items: center; text-align: center;
|
|
549
|
-
gap: 12px;
|
|
550
|
-
}
|
|
551
|
-
.badge {
|
|
552
|
-
width: 44px; height: 44px;
|
|
553
|
-
border-radius: 50%;
|
|
554
|
-
color: #ffffff;
|
|
555
|
-
font-size: 20px; font-weight: 700;
|
|
556
|
-
display: flex; align-items: center; justify-content: center;
|
|
557
|
-
flex-shrink: 0;
|
|
558
|
-
}
|
|
559
|
-
.card-title {
|
|
560
|
-
font-size: 26px; font-weight: 700; color: ${design.text_color};
|
|
561
|
-
line-height: 1.3;
|
|
562
|
-
}
|
|
563
|
-
.card-desc {
|
|
564
|
-
font-size: 22px; font-weight: 400; color: ${design.text_color}aa;
|
|
565
|
-
line-height: 1.5;
|
|
566
|
-
}
|
|
567
|
-
.page-num {
|
|
568
|
-
position: absolute;
|
|
569
|
-
bottom: 24px; right: 44px;
|
|
570
|
-
font-size: 13px;
|
|
571
|
-
color: ${design.text_color}55;
|
|
447
|
+
font-family: "${design.font_body}", "Malgun Gothic", sans-serif;
|
|
448
|
+
padding: 80px 100px;
|
|
449
|
+
display: flex;
|
|
450
|
+
flex-direction: column;
|
|
572
451
|
}
|
|
452
|
+
.header { margin-bottom: 48px; }
|
|
453
|
+
.slide-num { font-size: 14px; color: ${design.accent_color}; font-weight: 600; letter-spacing: 2px; text-transform: uppercase; margin-bottom: 12px; }
|
|
454
|
+
h1 { font-size: 52px; font-weight: 700; color: ${design.text_color}; font-family: "${design.font_title}", "Segoe UI", sans-serif; line-height: 1.2; }
|
|
455
|
+
.accent-bar { width: 80px; height: 4px; background: ${design.accent_color}; margin-top: 20px; border-radius: 2px; }
|
|
456
|
+
.content { flex: 1; display: grid; ${gridCols}; gap: 24px; align-content: center; }
|
|
457
|
+
.point { display: flex; align-items: flex-start; gap: 20px; padding: 28px 32px; background: #fff; border-radius: 16px; box-shadow: 0 4px 16px rgba(0,0,0,0.06); }
|
|
458
|
+
.point-num { width: 48px; height: 48px; border-radius: 50%; background: ${design.accent_color}; color: #fff; display: flex; align-items: center; justify-content: center; font-size: 24px; font-weight: 700; flex-shrink: 0; }
|
|
459
|
+
.point-text { font-size: 26px; line-height: 1.5; color: ${design.text_color}; flex: 1; }
|
|
573
460
|
</style>
|
|
574
461
|
</head>
|
|
575
462
|
<body>
|
|
576
463
|
<div class="header">
|
|
577
|
-
<div class="
|
|
578
|
-
|
|
464
|
+
<div class="slide-num">SLIDE ${slideNum}</div>
|
|
465
|
+
<h1>${escapeHtml(title)}</h1>
|
|
466
|
+
<div class="accent-bar"></div>
|
|
579
467
|
</div>
|
|
580
468
|
<div class="content">
|
|
581
|
-
|
|
582
|
-
${topRow.map((item, idx) => renderCard(item, idx)).join('')}
|
|
583
|
-
</div>
|
|
584
|
-
${bottomRow.length > 0 ? `<div class="row">
|
|
585
|
-
${bottomRow.map((item, idx) => renderCard(item, topRow.length + idx)).join('')}
|
|
586
|
-
</div>` : ''}
|
|
469
|
+
${pointsHtml}
|
|
587
470
|
</div>
|
|
588
|
-
<div class="page-num">${slideNum}</div>
|
|
589
471
|
</body>
|
|
590
472
|
</html>`;
|
|
591
473
|
}
|
|
@@ -683,67 +565,60 @@ async function runDesignPhase(llmClient, instruction, phaseLogger) {
|
|
|
683
565
|
}
|
|
684
566
|
return plan;
|
|
685
567
|
}
|
|
568
|
+
function extractHtmlFromResponse(raw) {
|
|
569
|
+
const trimmed = raw.trim();
|
|
570
|
+
if (/^<!DOCTYPE/i.test(trimmed) || /^<html/i.test(trimmed)) {
|
|
571
|
+
return trimmed;
|
|
572
|
+
}
|
|
573
|
+
const fenceMatch = trimmed.match(/```(?:html)?\s*\n?(<!DOCTYPE[\s\S]*?<\/html>)\s*\n?```/i);
|
|
574
|
+
if (fenceMatch)
|
|
575
|
+
return fenceMatch[1];
|
|
576
|
+
const htmlMatch = trimmed.match(/(<!DOCTYPE[\s\S]*<\/html>)/i);
|
|
577
|
+
if (htmlMatch)
|
|
578
|
+
return htmlMatch[1];
|
|
579
|
+
const htmlTagMatch = trimmed.match(/(<html[\s\S]*<\/html>)/i);
|
|
580
|
+
if (htmlTagMatch)
|
|
581
|
+
return '<!DOCTYPE html>\n' + htmlTagMatch[1];
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
686
584
|
async function generateSingleSlideHtml(llmClient, slide, design, slideIndex, totalSlides, language) {
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
const
|
|
585
|
+
const contentDirection = (slide.content_direction || '').trim();
|
|
586
|
+
logger.info(`Slide ${slideIndex + 1}: Generating free-form HTML for "${slide.title}"`);
|
|
587
|
+
const htmlPrompt = buildFreeHtmlPrompt(slide.title, contentDirection, design, slideIndex, totalSlides, language);
|
|
690
588
|
try {
|
|
691
589
|
const res = await llmClient.chatCompletion({
|
|
692
590
|
messages: [
|
|
693
|
-
{ role: 'system', content:
|
|
694
|
-
{ role: 'user', content: '
|
|
591
|
+
{ role: 'system', content: htmlPrompt },
|
|
592
|
+
{ role: 'user', content: 'Output the complete HTML now.' },
|
|
695
593
|
],
|
|
696
|
-
temperature: 0.
|
|
697
|
-
max_tokens:
|
|
594
|
+
temperature: 0.6,
|
|
595
|
+
max_tokens: 4000,
|
|
698
596
|
});
|
|
699
597
|
const msg = res.choices[0]?.message;
|
|
700
598
|
const rawHtml = msg ? extractContent(msg) : '';
|
|
701
|
-
|
|
702
|
-
if (html) {
|
|
703
|
-
|
|
704
|
-
if (validation.pass) {
|
|
705
|
-
return { html, isCodeTemplate: false };
|
|
706
|
-
}
|
|
707
|
-
logger.info(`Slide ${slideIndex + 1}: Direct HTML failed validation: ${validation.feedback}`);
|
|
599
|
+
let html = extractHtmlFromResponse(rawHtml);
|
|
600
|
+
if (html && !hasPlaceholderText(html)) {
|
|
601
|
+
return { html, isCodeTemplate: false };
|
|
708
602
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
logger.warn(`Slide ${slideIndex + 1}: Direct HTML LLM call failed: ${e}`);
|
|
712
|
-
}
|
|
713
|
-
logger.info(`Slide ${slideIndex + 1}: Falling back to code template "${layoutType}"`);
|
|
714
|
-
const jsonPrompt = buildContentFillJsonPrompt(slide.title, cleanedDirection, layoutType, language);
|
|
715
|
-
try {
|
|
716
|
-
const jsonRes = await llmClient.chatCompletion({
|
|
603
|
+
logger.warn(`Slide ${slideIndex + 1}: First HTML attempt failed. Raw length: ${rawHtml.length}. Retrying...`);
|
|
604
|
+
const retryRes = await llmClient.chatCompletion({
|
|
717
605
|
messages: [
|
|
718
|
-
{ role: 'system', content:
|
|
719
|
-
{ role: 'user', content: 'Output the
|
|
606
|
+
{ role: 'system', content: htmlPrompt },
|
|
607
|
+
{ role: 'user', content: 'Output ONLY the complete HTML document. Start with <!DOCTYPE html> and end with </html>. No explanation.' },
|
|
720
608
|
],
|
|
721
|
-
temperature: 0.
|
|
722
|
-
max_tokens:
|
|
609
|
+
temperature: 0.4,
|
|
610
|
+
max_tokens: 4000,
|
|
723
611
|
});
|
|
724
|
-
const
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
if (!
|
|
728
|
-
|
|
729
|
-
messages: [
|
|
730
|
-
{ role: 'system', content: jsonPrompt },
|
|
731
|
-
{ role: 'user', content: 'Output ONLY valid JSON. No markdown fences, no explanation. Start with { and end with }.' },
|
|
732
|
-
],
|
|
733
|
-
temperature: 0.2,
|
|
734
|
-
max_tokens: 2000,
|
|
735
|
-
});
|
|
736
|
-
const retryMsg = retryRes.choices[0]?.message;
|
|
737
|
-
const retryRaw = retryMsg ? extractContent(retryMsg) : '';
|
|
738
|
-
slideData = parseContentFillJson(retryRaw, layoutType);
|
|
739
|
-
}
|
|
740
|
-
if (slideData) {
|
|
741
|
-
const html = buildContentSlideHtml(design, slide.title, layoutType, slideData, slideIndex + 1, slideIndex);
|
|
742
|
-
return { html, isCodeTemplate: true };
|
|
612
|
+
const retryMsg = retryRes.choices[0]?.message;
|
|
613
|
+
const retryRaw = retryMsg ? extractContent(retryMsg) : '';
|
|
614
|
+
html = extractHtmlFromResponse(retryRaw);
|
|
615
|
+
if (html && !hasPlaceholderText(html)) {
|
|
616
|
+
return { html, isCodeTemplate: false };
|
|
743
617
|
}
|
|
618
|
+
logger.warn(`Slide ${slideIndex + 1}: Both HTML attempts failed.`);
|
|
744
619
|
}
|
|
745
620
|
catch (e) {
|
|
746
|
-
logger.warn(`Slide ${slideIndex + 1}:
|
|
621
|
+
logger.warn(`Slide ${slideIndex + 1}: HTML generation error: ${e}`);
|
|
747
622
|
}
|
|
748
623
|
return null;
|
|
749
624
|
}
|
|
@@ -766,18 +641,6 @@ async function generateAllHtml(llmClient, plan, companyName, titleSubtitle, kstD
|
|
|
766
641
|
isCodeTemplate: true,
|
|
767
642
|
});
|
|
768
643
|
}
|
|
769
|
-
else if (isOverviewSlide(slide.title, i)) {
|
|
770
|
-
const overviewItems = parseOverviewItems(slide.content_direction || '');
|
|
771
|
-
if (overviewItems.length >= 2) {
|
|
772
|
-
const firstLine = (slide.content_direction || '').split('\n')[0] || '';
|
|
773
|
-
const overviewSubtitle = /^\d/.test(firstLine.trim()) ? '' : firstLine.trim();
|
|
774
|
-
results.set(i, {
|
|
775
|
-
index: i,
|
|
776
|
-
html: buildOverviewSlideHtml(plan.design, slide.title, overviewSubtitle, overviewItems, i + 1),
|
|
777
|
-
isCodeTemplate: true,
|
|
778
|
-
});
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
644
|
}
|
|
782
645
|
const contentIndices = plan.slides
|
|
783
646
|
.map((s, i) => ({ slide: s, index: i }))
|
|
@@ -797,6 +660,12 @@ async function generateAllHtml(llmClient, plan, companyName, titleSubtitle, kstD
|
|
|
797
660
|
if (result) {
|
|
798
661
|
results.set(index, { index, html: result.html, isCodeTemplate: result.isCodeTemplate });
|
|
799
662
|
}
|
|
663
|
+
else {
|
|
664
|
+
const slide = plan.slides[index];
|
|
665
|
+
const fallbackHtml = buildFallbackSlideHtml(plan.design, slide.title, slide.content_direction || '', index + 1);
|
|
666
|
+
results.set(index, { index, html: fallbackHtml, isCodeTemplate: true });
|
|
667
|
+
logger.warn(`Slide ${index + 1}: Using fallback HTML for "${slide.title}"`);
|
|
668
|
+
}
|
|
800
669
|
}
|
|
801
670
|
const done = Math.min(batch + MAX_CONCURRENT, contentIndices.length);
|
|
802
671
|
if (phaseLogger)
|
|
@@ -810,8 +679,7 @@ async function validateAndRegenerate(llmClient, htmlResults, plan, language, pha
|
|
|
810
679
|
const slide = plan.slides[index];
|
|
811
680
|
if (slide.type === 'title' || slide.type === 'closing')
|
|
812
681
|
continue;
|
|
813
|
-
const
|
|
814
|
-
const validation = validateSlideHtml(result.html, layoutType);
|
|
682
|
+
const validation = validateSlideHtml(result.html);
|
|
815
683
|
if (!validation.pass) {
|
|
816
684
|
logger.info(`Slide ${index + 1}: Post-validation failed: ${validation.feedback}`);
|
|
817
685
|
failedIndices.push(index);
|
|
@@ -853,23 +721,28 @@ async function validateAndRegenerate(llmClient, htmlResults, plan, language, pha
|
|
|
853
721
|
}
|
|
854
722
|
return htmlResults;
|
|
855
723
|
}
|
|
856
|
-
|
|
724
|
+
const MAX_SCREENSHOT_CONCURRENT = 4;
|
|
725
|
+
async function assemblePresentation(htmlResults, plan, timestamp, savePath, companyName, language, phaseLogger, toolCallLogger) {
|
|
857
726
|
const { writePath: tempWritePath, winPath: tempWinPath } = getTempDir();
|
|
858
727
|
ensureTempDir(tempWritePath);
|
|
728
|
+
const totalSlides = htmlResults.size;
|
|
729
|
+
const sortedEntries = [...htmlResults.entries()].sort((a, b) => a[0] - b[0]);
|
|
859
730
|
const builtSlides = [];
|
|
860
|
-
let failCount = 0;
|
|
861
731
|
let totalToolCalls = 0;
|
|
862
732
|
const tempFiles = [];
|
|
863
|
-
const
|
|
733
|
+
const initCountRes = await powerpointClient.powerpointGetSlideCount();
|
|
734
|
+
const initSlideCount = initCountRes['slide_count'] || 1;
|
|
735
|
+
const slidesToAdd = Math.max(0, totalSlides - initSlideCount);
|
|
736
|
+
if (phaseLogger)
|
|
737
|
+
phaseLogger('powerpoint-create', 'assembly', `Pre-creating ${totalSlides} slides (existing: ${initSlideCount}, adding: ${slidesToAdd})...`);
|
|
738
|
+
for (let i = 0; i < slidesToAdd; i++) {
|
|
739
|
+
await powerpointClient.powerpointAddSlide(7);
|
|
740
|
+
totalToolCalls++;
|
|
741
|
+
}
|
|
742
|
+
const slideFiles = new Map();
|
|
743
|
+
let slideNum = 0;
|
|
864
744
|
for (const [index, result] of sortedEntries) {
|
|
865
|
-
|
|
866
|
-
const slideNum = builtSlides.length + 1;
|
|
867
|
-
if (failCount >= 3) {
|
|
868
|
-
logger.warn('Too many slide failures, stopping');
|
|
869
|
-
break;
|
|
870
|
-
}
|
|
871
|
-
if (phaseLogger)
|
|
872
|
-
phaseLogger('powerpoint-create', 'assembly', `Rendering slide ${slideNum}: ${slidePlan.title}`);
|
|
745
|
+
slideNum++;
|
|
873
746
|
const htmlFileName = `hanseol_slide_${slideNum}_${timestamp}.html`;
|
|
874
747
|
const pngFileName = `hanseol_slide_${slideNum}_${timestamp}.png`;
|
|
875
748
|
const htmlWritePath = path.join(tempWritePath, htmlFileName);
|
|
@@ -877,101 +750,201 @@ async function assemblePresentation(htmlResults, plan, timestamp, savePath, phas
|
|
|
877
750
|
const htmlWinPath = `${tempWinPath}\\${htmlFileName}`;
|
|
878
751
|
const pngWinPath = `${tempWinPath}\\${pngFileName}`;
|
|
879
752
|
try {
|
|
880
|
-
const viewportHtml = result.
|
|
881
|
-
|
|
882
|
-
: injectViewportCss(result.html, plan.design.background_color);
|
|
883
|
-
const processed = injectTitleContrastFix(viewportHtml, plan.design.text_color);
|
|
884
|
-
fs.writeFileSync(htmlWritePath, processed, 'utf-8');
|
|
753
|
+
const viewportHtml = injectEdgeSizing(result.html, plan.design.background_color);
|
|
754
|
+
fs.writeFileSync(htmlWritePath, viewportHtml, 'utf-8');
|
|
885
755
|
tempFiles.push(htmlWritePath);
|
|
756
|
+
slideFiles.set(index, { slideNum, htmlWritePath, pngWritePath, htmlWinPath, pngWinPath });
|
|
886
757
|
}
|
|
887
758
|
catch (e) {
|
|
888
|
-
logger.warn(`Slide ${slideNum}: Failed to write HTML
|
|
889
|
-
failCount++;
|
|
890
|
-
continue;
|
|
759
|
+
logger.warn(`Slide ${slideNum}: Failed to write HTML: ${e}`);
|
|
891
760
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
renderSuccess = retryRender.success;
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
catch (e) {
|
|
905
|
-
logger.warn(`Slide ${slideNum}: Edge screenshot failed: ${e}`);
|
|
761
|
+
}
|
|
762
|
+
if (phaseLogger)
|
|
763
|
+
phaseLogger('powerpoint-create', 'assembly', `Rendering ${slideFiles.size} screenshots in parallel (batch ${MAX_SCREENSHOT_CONCURRENT})...`);
|
|
764
|
+
const screenshotEntries = [...slideFiles.entries()].sort((a, b) => a[0] - b[0]);
|
|
765
|
+
const screenshotSuccess = new Set();
|
|
766
|
+
for (let batch = 0; batch < screenshotEntries.length; batch += MAX_SCREENSHOT_CONCURRENT) {
|
|
767
|
+
const chunk = screenshotEntries.slice(batch, batch + MAX_SCREENSHOT_CONCURRENT);
|
|
768
|
+
const batchResults = await Promise.all(chunk.map(async ([index, files]) => {
|
|
769
|
+
let success = false;
|
|
906
770
|
try {
|
|
907
|
-
|
|
908
|
-
const retryRender = await powerpointClient.renderHtmlToImage(htmlWinPath, pngWinPath);
|
|
771
|
+
const result = await powerpointClient.renderHtmlToImage(files.htmlWinPath, files.pngWinPath);
|
|
909
772
|
totalToolCalls++;
|
|
910
|
-
|
|
773
|
+
success = result.success;
|
|
774
|
+
if (!success) {
|
|
775
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
776
|
+
const retry = await powerpointClient.renderHtmlToImage(files.htmlWinPath, files.pngWinPath);
|
|
777
|
+
totalToolCalls++;
|
|
778
|
+
success = retry.success;
|
|
779
|
+
}
|
|
911
780
|
}
|
|
912
781
|
catch {
|
|
913
|
-
|
|
782
|
+
try {
|
|
783
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
784
|
+
const retry = await powerpointClient.renderHtmlToImage(files.htmlWinPath, files.pngWinPath);
|
|
785
|
+
totalToolCalls++;
|
|
786
|
+
success = retry.success;
|
|
787
|
+
}
|
|
788
|
+
catch {
|
|
789
|
+
success = false;
|
|
790
|
+
}
|
|
914
791
|
}
|
|
792
|
+
try {
|
|
793
|
+
fs.unlinkSync(files.htmlWritePath);
|
|
794
|
+
}
|
|
795
|
+
catch { }
|
|
796
|
+
if (success) {
|
|
797
|
+
try {
|
|
798
|
+
const stat = fs.statSync(files.pngWritePath);
|
|
799
|
+
if (stat.size < 15000) {
|
|
800
|
+
logger.warn(`Slide ${files.slideNum}: Screenshot too small (${stat.size} bytes)`);
|
|
801
|
+
success = false;
|
|
802
|
+
try {
|
|
803
|
+
fs.unlinkSync(files.pngWritePath);
|
|
804
|
+
}
|
|
805
|
+
catch { }
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
catch { }
|
|
809
|
+
}
|
|
810
|
+
if (success)
|
|
811
|
+
tempFiles.push(files.pngWritePath);
|
|
812
|
+
return { index, success };
|
|
813
|
+
}));
|
|
814
|
+
for (const { index, success } of batchResults) {
|
|
815
|
+
if (success)
|
|
816
|
+
screenshotSuccess.add(index);
|
|
915
817
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
818
|
+
const done = Math.min(batch + MAX_SCREENSHOT_CONCURRENT, screenshotEntries.length);
|
|
819
|
+
if (phaseLogger)
|
|
820
|
+
phaseLogger('powerpoint-create', 'assembly', `Screenshots: ${done}/${screenshotEntries.length} done`);
|
|
821
|
+
}
|
|
822
|
+
if (phaseLogger)
|
|
823
|
+
phaseLogger('powerpoint-create', 'assembly', `Filling ${totalSlides} slides...`);
|
|
824
|
+
const unfilledSlideNums = [];
|
|
825
|
+
for (const [index] of sortedEntries) {
|
|
826
|
+
const files = slideFiles.get(index);
|
|
827
|
+
const slidePlan = plan.slides[index];
|
|
828
|
+
const htmlResult = htmlResults.get(index);
|
|
829
|
+
if (!files) {
|
|
830
|
+
const posInSorted = sortedEntries.findIndex(([idx]) => idx === index);
|
|
831
|
+
const inferredSlideNum = posInSorted >= 0 ? posInSorted + 1 : -1;
|
|
832
|
+
logger.warn(`Slide index ${index} (slideNum ${inferredSlideNum}): No file info, marking for deletion`);
|
|
833
|
+
if (inferredSlideNum > 0)
|
|
834
|
+
unfilledSlideNums.push(inferredSlideNum);
|
|
923
835
|
continue;
|
|
924
836
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
837
|
+
let filled = false;
|
|
838
|
+
if (screenshotSuccess.has(index)) {
|
|
839
|
+
const bgResult = await powerpointClient.powerpointAddFullSlideImage(files.slideNum, files.pngWinPath);
|
|
840
|
+
totalToolCalls++;
|
|
841
|
+
if (toolCallLogger)
|
|
842
|
+
toolCallLogger('powerpoint-create', 'addFullSlideImage', { slideNum: files.slideNum, imagePath: files.pngWinPath }, bgResult.success ? 'OK' : 'Failed', bgResult.success, files.slideNum, totalToolCalls);
|
|
843
|
+
filled = bgResult.success;
|
|
844
|
+
}
|
|
845
|
+
if (!filled) {
|
|
846
|
+
logger.info(`Slide ${files.slideNum}: Primary screenshot failed, trying fallback rendering...`);
|
|
847
|
+
const slide = plan.slides[index];
|
|
848
|
+
const fallbackHtml = buildFallbackSlideHtml(plan.design, slide.title, slide.content_direction || '', files.slideNum);
|
|
849
|
+
const fbHtmlName = `hanseol_fb_${files.slideNum}_${timestamp}.html`;
|
|
850
|
+
const fbPngName = `hanseol_fb_${files.slideNum}_${timestamp}.png`;
|
|
851
|
+
const fbHtmlWrite = path.join(tempWritePath, fbHtmlName);
|
|
852
|
+
const fbPngWrite = path.join(tempWritePath, fbPngName);
|
|
853
|
+
const fbHtmlWin = `${tempWinPath}\\${fbHtmlName}`;
|
|
854
|
+
const fbPngWin = `${tempWinPath}\\${fbPngName}`;
|
|
855
|
+
try {
|
|
856
|
+
const viewportHtml = injectEdgeSizing(fallbackHtml, plan.design.background_color);
|
|
857
|
+
fs.writeFileSync(fbHtmlWrite, viewportHtml, 'utf-8');
|
|
858
|
+
const fbResult = await powerpointClient.renderHtmlToImage(fbHtmlWin, fbPngWin);
|
|
859
|
+
totalToolCalls++;
|
|
930
860
|
try {
|
|
931
|
-
fs.unlinkSync(
|
|
861
|
+
fs.unlinkSync(fbHtmlWrite);
|
|
862
|
+
}
|
|
863
|
+
catch { }
|
|
864
|
+
if (fbResult.success) {
|
|
865
|
+
const bgResult = await powerpointClient.powerpointAddFullSlideImage(files.slideNum, fbPngWin);
|
|
866
|
+
totalToolCalls++;
|
|
867
|
+
filled = bgResult.success;
|
|
868
|
+
try {
|
|
869
|
+
fs.unlinkSync(fbPngWrite);
|
|
870
|
+
}
|
|
871
|
+
catch { }
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
catch (e) {
|
|
875
|
+
logger.warn(`Slide ${files.slideNum}: Fallback rendering also failed: ${e}`);
|
|
876
|
+
try {
|
|
877
|
+
fs.unlinkSync(fbHtmlWrite);
|
|
932
878
|
}
|
|
933
879
|
catch { }
|
|
934
|
-
continue;
|
|
935
880
|
}
|
|
936
881
|
}
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
const addResult = await powerpointClient.powerpointAddSlide(7);
|
|
940
|
-
totalToolCalls++;
|
|
941
|
-
if (!addResult.success) {
|
|
942
|
-
logger.warn(`Slide ${slideNum}: Failed to add blank slide`);
|
|
943
|
-
failCount++;
|
|
944
|
-
continue;
|
|
945
|
-
}
|
|
946
|
-
const bgResult = await powerpointClient.powerpointAddFullSlideImage(slideNum, pngWinPath);
|
|
947
|
-
totalToolCalls++;
|
|
948
|
-
if (toolCallLogger)
|
|
949
|
-
toolCallLogger('powerpoint-create', 'addFullSlideImage', { slideNum, imagePath: pngWinPath }, bgResult.success ? 'OK' : 'Failed', bgResult.success, slideNum, totalToolCalls);
|
|
950
|
-
if (bgResult.success) {
|
|
951
|
-
builtSlides.push(`Slide ${slideNum}: ${slidePlan.title} (${slidePlan.type})`);
|
|
882
|
+
if (filled) {
|
|
883
|
+
builtSlides.push(`Slide ${files.slideNum}: ${slidePlan.title} (${slidePlan.type})`);
|
|
952
884
|
try {
|
|
953
|
-
await powerpointClient.powerpointAddNote(slideNum,
|
|
885
|
+
await powerpointClient.powerpointAddNote(files.slideNum, htmlResult.html);
|
|
954
886
|
}
|
|
955
887
|
catch { }
|
|
956
888
|
}
|
|
957
889
|
else {
|
|
958
|
-
logger.warn(`Slide ${slideNum}:
|
|
959
|
-
|
|
890
|
+
logger.warn(`Slide ${files.slideNum}: All rendering attempts failed, marking for deletion`);
|
|
891
|
+
unfilledSlideNums.push(files.slideNum);
|
|
960
892
|
}
|
|
961
893
|
}
|
|
962
|
-
if (
|
|
894
|
+
if (unfilledSlideNums.length > 0) {
|
|
895
|
+
const sortedDesc = [...unfilledSlideNums].sort((a, b) => b - a);
|
|
896
|
+
for (const slideNum of sortedDesc) {
|
|
897
|
+
try {
|
|
898
|
+
await powerpointClient.powerpointDeleteSlide(slideNum);
|
|
899
|
+
totalToolCalls++;
|
|
900
|
+
logger.info(`Deleted unfilled slide ${slideNum}`);
|
|
901
|
+
}
|
|
902
|
+
catch (e) {
|
|
903
|
+
logger.warn(`Failed to delete unfilled slide ${slideNum}: ${e}`);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
const hasClosingInPlan = plan.slides.some(s => s.type === 'closing');
|
|
908
|
+
const hasClosingBuilt = builtSlides.some(s => s.includes('(closing)'));
|
|
909
|
+
if (hasClosingInPlan && !hasClosingBuilt && builtSlides.length > 0) {
|
|
910
|
+
logger.info('Closing slide was lost during assembly — adding it now');
|
|
963
911
|
try {
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
912
|
+
await powerpointClient.powerpointAddSlide(7);
|
|
913
|
+
totalToolCalls++;
|
|
914
|
+
const slideCountRes = await powerpointClient.powerpointGetSlideCount();
|
|
915
|
+
const newSlideNum = slideCountRes['slide_count'] || builtSlides.length + 1;
|
|
916
|
+
const closingPlan = plan.slides.find(s => s.type === 'closing');
|
|
917
|
+
const closingTagline = (closingPlan.content_direction || '').replace(/감사합니다|thank\s*you/gi, '').trim() || undefined;
|
|
918
|
+
const closingHtml = buildClosingSlideHtml(plan.design, companyName, newSlideNum, language, closingTagline);
|
|
919
|
+
const clHtmlName = `hanseol_closing_${timestamp}.html`;
|
|
920
|
+
const clPngName = `hanseol_closing_${timestamp}.png`;
|
|
921
|
+
const clHtmlWrite = path.join(tempWritePath, clHtmlName);
|
|
922
|
+
const clPngWrite = path.join(tempWritePath, clPngName);
|
|
923
|
+
const clHtmlWin = `${tempWinPath}\\${clHtmlName}`;
|
|
924
|
+
const clPngWin = `${tempWinPath}\\${clPngName}`;
|
|
925
|
+
const viewportHtml = injectEdgeSizing(closingHtml, plan.design.background_color);
|
|
926
|
+
fs.writeFileSync(clHtmlWrite, viewportHtml, 'utf-8');
|
|
927
|
+
const ssResult = await powerpointClient.renderHtmlToImage(clHtmlWin, clPngWin);
|
|
928
|
+
totalToolCalls++;
|
|
929
|
+
try {
|
|
930
|
+
fs.unlinkSync(clHtmlWrite);
|
|
931
|
+
}
|
|
932
|
+
catch { }
|
|
933
|
+
if (ssResult.success) {
|
|
934
|
+
const bgResult = await powerpointClient.powerpointAddFullSlideImage(newSlideNum, clPngWin);
|
|
935
|
+
totalToolCalls++;
|
|
936
|
+
if (bgResult.success) {
|
|
937
|
+
builtSlides.push(`Slide ${newSlideNum}: ${closingPlan.title} (closing)`);
|
|
938
|
+
logger.info('Closing slide added successfully');
|
|
939
|
+
}
|
|
940
|
+
try {
|
|
941
|
+
fs.unlinkSync(clPngWrite);
|
|
970
942
|
}
|
|
943
|
+
catch { }
|
|
971
944
|
}
|
|
972
945
|
}
|
|
973
946
|
catch (e) {
|
|
974
|
-
logger.warn(`Failed to
|
|
947
|
+
logger.warn(`Failed to add closing slide: ${e}`);
|
|
975
948
|
}
|
|
976
949
|
}
|
|
977
950
|
if (builtSlides.length > 0) {
|
|
@@ -1084,7 +1057,8 @@ async function runStructured(llmClient, instruction, explicitSavePath) {
|
|
|
1084
1057
|
if (/로고|슬로건|연락처|contact|logo|placeholder/i.test(titleSubtitle)) {
|
|
1085
1058
|
titleSubtitle = '';
|
|
1086
1059
|
}
|
|
1087
|
-
const
|
|
1060
|
+
const cleanInstruction = instruction.replace(/\*\*/g, '');
|
|
1061
|
+
const companyMatch = cleanInstruction.match(/회사명\s*[::]?\s*([^\s,,、]+)/);
|
|
1088
1062
|
if (companyMatch && companyMatch[1]) {
|
|
1089
1063
|
const companyName_ = companyMatch[1];
|
|
1090
1064
|
if (titleSlidePlan && titleSlidePlan.title.trim() !== companyName_) {
|
|
@@ -1104,7 +1078,7 @@ async function runStructured(llmClient, instruction, explicitSavePath) {
|
|
|
1104
1078
|
const validatedResults = await validateAndRegenerate(llmClient, htmlResults, plan, language, phaseLogger);
|
|
1105
1079
|
if (phaseLogger)
|
|
1106
1080
|
phaseLogger('powerpoint-create', 'assembly', `Assembling ${validatedResults.size} slides into PowerPoint...`);
|
|
1107
|
-
const { builtSlides, totalToolCalls } = await assemblePresentation(validatedResults, plan, timestamp, savePath, phaseLogger, toolCallLogger);
|
|
1081
|
+
const { builtSlides, totalToolCalls } = await assemblePresentation(validatedResults, plan, timestamp, savePath, companyName, language, phaseLogger, toolCallLogger);
|
|
1108
1082
|
const duration = Date.now() - startTime;
|
|
1109
1083
|
const slideList = builtSlides.join('\n');
|
|
1110
1084
|
const summary = `Presentation COMPLETE — ${builtSlides.length} slides created and saved successfully.\nAll requested topics are covered across these slides. Do NOT add more slides or call powerpoint_modify_agent.\n\nSlides:\n${slideList}`;
|