hanseol-dev 5.0.2-dev.8 → 5.0.2-dev.81

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 (122) hide show
  1. package/dist/agents/common/sub-agent.d.ts +21 -0
  2. package/dist/agents/common/sub-agent.d.ts.map +1 -1
  3. package/dist/agents/common/sub-agent.js +195 -10
  4. package/dist/agents/common/sub-agent.js.map +1 -1
  5. package/dist/agents/index.d.ts +1 -1
  6. package/dist/agents/index.d.ts.map +1 -1
  7. package/dist/agents/index.js +1 -1
  8. package/dist/agents/index.js.map +1 -1
  9. package/dist/agents/office/excel-agent.d.ts +1 -1
  10. package/dist/agents/office/excel-agent.d.ts.map +1 -1
  11. package/dist/agents/office/excel-agent.js +6 -6
  12. package/dist/agents/office/excel-agent.js.map +1 -1
  13. package/dist/agents/office/excel-create-agent.d.ts +3 -0
  14. package/dist/agents/office/excel-create-agent.d.ts.map +1 -0
  15. package/dist/agents/office/excel-create-agent.js +38 -0
  16. package/dist/agents/office/excel-create-agent.js.map +1 -0
  17. package/dist/agents/office/excel-create-prompts.d.ts +4 -0
  18. package/dist/agents/office/excel-create-prompts.d.ts.map +1 -0
  19. package/dist/agents/office/excel-create-prompts.js +154 -0
  20. package/dist/agents/office/excel-create-prompts.js.map +1 -0
  21. package/dist/agents/office/index.d.ts +6 -3
  22. package/dist/agents/office/index.d.ts.map +1 -1
  23. package/dist/agents/office/index.js +6 -3
  24. package/dist/agents/office/index.js.map +1 -1
  25. package/dist/agents/office/powerpoint-agent.d.ts +1 -1
  26. package/dist/agents/office/powerpoint-agent.d.ts.map +1 -1
  27. package/dist/agents/office/powerpoint-agent.js +6 -6
  28. package/dist/agents/office/powerpoint-agent.js.map +1 -1
  29. package/dist/agents/office/powerpoint-create-agent.d.ts +3 -0
  30. package/dist/agents/office/powerpoint-create-agent.d.ts.map +1 -0
  31. package/dist/agents/office/powerpoint-create-agent.js +828 -0
  32. package/dist/agents/office/powerpoint-create-agent.js.map +1 -0
  33. package/dist/agents/office/powerpoint-create-prompts.d.ts +19 -0
  34. package/dist/agents/office/powerpoint-create-prompts.d.ts.map +1 -0
  35. package/dist/agents/office/powerpoint-create-prompts.js +358 -0
  36. package/dist/agents/office/powerpoint-create-prompts.js.map +1 -0
  37. package/dist/agents/office/prompts.d.ts +10 -3
  38. package/dist/agents/office/prompts.d.ts.map +1 -1
  39. package/dist/agents/office/prompts.js +451 -52
  40. package/dist/agents/office/prompts.js.map +1 -1
  41. package/dist/agents/office/word-agent.d.ts +1 -1
  42. package/dist/agents/office/word-agent.d.ts.map +1 -1
  43. package/dist/agents/office/word-agent.js +6 -6
  44. package/dist/agents/office/word-agent.js.map +1 -1
  45. package/dist/agents/office/word-create-agent.d.ts +3 -0
  46. package/dist/agents/office/word-create-agent.d.ts.map +1 -0
  47. package/dist/agents/office/word-create-agent.js +38 -0
  48. package/dist/agents/office/word-create-agent.js.map +1 -0
  49. package/dist/agents/office/word-create-prompts.d.ts +4 -0
  50. package/dist/agents/office/word-create-prompts.d.ts.map +1 -0
  51. package/dist/agents/office/word-create-prompts.js +164 -0
  52. package/dist/agents/office/word-create-prompts.js.map +1 -0
  53. package/dist/cli.js +3 -0
  54. package/dist/cli.js.map +1 -1
  55. package/dist/constants.d.ts +1 -1
  56. package/dist/constants.d.ts.map +1 -1
  57. package/dist/constants.js +1 -1
  58. package/dist/constants.js.map +1 -1
  59. package/dist/pipe/pipe-runner.d.ts.map +1 -1
  60. package/dist/pipe/pipe-runner.js +20 -0
  61. package/dist/pipe/pipe-runner.js.map +1 -1
  62. package/dist/prompts/agents/planning.d.ts.map +1 -1
  63. package/dist/prompts/agents/planning.js +43 -15
  64. package/dist/prompts/agents/planning.js.map +1 -1
  65. package/dist/prompts/shared/tool-usage.d.ts.map +1 -1
  66. package/dist/prompts/shared/tool-usage.js +6 -3
  67. package/dist/prompts/shared/tool-usage.js.map +1 -1
  68. package/dist/prompts/system/plan-execute.js +1 -1
  69. package/dist/tools/office/excel-client.js +4 -4
  70. package/dist/tools/office/excel-tools/index.d.ts +2 -0
  71. package/dist/tools/office/excel-tools/index.d.ts.map +1 -1
  72. package/dist/tools/office/excel-tools/index.js +12 -0
  73. package/dist/tools/office/excel-tools/index.js.map +1 -1
  74. package/dist/tools/office/excel-tools/sheet-builders.d.ts +8 -0
  75. package/dist/tools/office/excel-tools/sheet-builders.d.ts.map +1 -0
  76. package/dist/tools/office/excel-tools/sheet-builders.js +414 -0
  77. package/dist/tools/office/excel-tools/sheet-builders.js.map +1 -0
  78. package/dist/tools/office/excel-tools.d.ts +1 -1
  79. package/dist/tools/office/excel-tools.d.ts.map +1 -1
  80. package/dist/tools/office/excel-tools.js +1 -1
  81. package/dist/tools/office/excel-tools.js.map +1 -1
  82. package/dist/tools/office/powerpoint-client.d.ts +2 -0
  83. package/dist/tools/office/powerpoint-client.d.ts.map +1 -1
  84. package/dist/tools/office/powerpoint-client.js +81 -10
  85. package/dist/tools/office/powerpoint-client.js.map +1 -1
  86. package/dist/tools/office/powerpoint-tools/export.d.ts.map +1 -1
  87. package/dist/tools/office/powerpoint-tools/export.js +16 -1
  88. package/dist/tools/office/powerpoint-tools/export.js.map +1 -1
  89. package/dist/tools/office/powerpoint-tools/index.d.ts +2 -0
  90. package/dist/tools/office/powerpoint-tools/index.d.ts.map +1 -1
  91. package/dist/tools/office/powerpoint-tools/index.js +11 -0
  92. package/dist/tools/office/powerpoint-tools/index.js.map +1 -1
  93. package/dist/tools/office/powerpoint-tools/launch.d.ts.map +1 -1
  94. package/dist/tools/office/powerpoint-tools/launch.js +2 -0
  95. package/dist/tools/office/powerpoint-tools/launch.js.map +1 -1
  96. package/dist/tools/office/powerpoint-tools/layout-builders.d.ts +12 -0
  97. package/dist/tools/office/powerpoint-tools/layout-builders.d.ts.map +1 -0
  98. package/dist/tools/office/powerpoint-tools/layout-builders.js +785 -0
  99. package/dist/tools/office/powerpoint-tools/layout-builders.js.map +1 -0
  100. package/dist/tools/office/powerpoint-tools/slides.js +1 -1
  101. package/dist/tools/office/powerpoint-tools/slides.js.map +1 -1
  102. package/dist/tools/office/powerpoint-tools.d.ts +1 -1
  103. package/dist/tools/office/powerpoint-tools.d.ts.map +1 -1
  104. package/dist/tools/office/powerpoint-tools.js +1 -1
  105. package/dist/tools/office/powerpoint-tools.js.map +1 -1
  106. package/dist/tools/office/word-client.js +4 -4
  107. package/dist/tools/office/word-tools/index.d.ts +4 -1
  108. package/dist/tools/office/word-tools/index.d.ts.map +1 -1
  109. package/dist/tools/office/word-tools/index.js +15 -0
  110. package/dist/tools/office/word-tools/index.js.map +1 -1
  111. package/dist/tools/office/word-tools/section-builders.d.ts +10 -0
  112. package/dist/tools/office/word-tools/section-builders.d.ts.map +1 -0
  113. package/dist/tools/office/word-tools/section-builders.js +421 -0
  114. package/dist/tools/office/word-tools/section-builders.js.map +1 -0
  115. package/dist/tools/office/word-tools.d.ts +1 -1
  116. package/dist/tools/office/word-tools.d.ts.map +1 -1
  117. package/dist/tools/office/word-tools.js +1 -1
  118. package/dist/tools/office/word-tools.js.map +1 -1
  119. package/dist/tools/registry.d.ts.map +1 -1
  120. package/dist/tools/registry.js +7 -4
  121. package/dist/tools/registry.js.map +1 -1
  122. package/package.json +1 -1
@@ -0,0 +1,828 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { POWERPOINT_CREATE_TOOLS } from '../../tools/office/powerpoint-tools.js';
4
+ import { powerpointClient } from '../../tools/office/powerpoint-client.js';
5
+ import { SubAgent } from '../common/sub-agent.js';
6
+ import { getSubAgentPhaseLogger, getSubAgentToolCallLogger } from '../common/sub-agent.js';
7
+ import { logger } from '../../utils/logger.js';
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
+ };
22
+ function extractContent(msg) {
23
+ const content = msg['content'];
24
+ if (content && content.trim())
25
+ return content;
26
+ const reasoning = msg['reasoning_content'];
27
+ if (reasoning && reasoning.trim())
28
+ return reasoning;
29
+ return '';
30
+ }
31
+ function validatePlan(plan) {
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)';
36
+ }
37
+ if (!Array.isArray(plan.slides) || plan.slides.length < 3) {
38
+ return 'slides array must have at least 3 entries';
39
+ }
40
+ if (plan.slides[0]?.type !== 'title') {
41
+ return 'First slide must be type "title"';
42
+ }
43
+ if (plan.slides[plan.slides.length - 1]?.type !== 'closing') {
44
+ return 'Last slide must be type "closing"';
45
+ }
46
+ for (let i = 0; i < plan.slides.length; i++) {
47
+ if (!plan.slides[i].title) {
48
+ return `Slide ${i + 1}: missing title`;
49
+ }
50
+ }
51
+ return null;
52
+ }
53
+ function repairLlmJson(raw) {
54
+ let result = '';
55
+ let inString = false;
56
+ let i = 0;
57
+ while (i < raw.length) {
58
+ const ch = raw[i];
59
+ if (!inString) {
60
+ if (ch === ',') {
61
+ let j = i + 1;
62
+ while (j < raw.length && /\s/.test(raw[j]))
63
+ j++;
64
+ if (j < raw.length && (raw[j] === '}' || raw[j] === ']')) {
65
+ i++;
66
+ continue;
67
+ }
68
+ }
69
+ result += ch;
70
+ if (ch === '"')
71
+ inString = true;
72
+ i++;
73
+ continue;
74
+ }
75
+ if (ch === '\\') {
76
+ result += ch;
77
+ if (i + 1 < raw.length) {
78
+ result += raw[i + 1];
79
+ i += 2;
80
+ }
81
+ else {
82
+ i++;
83
+ }
84
+ continue;
85
+ }
86
+ if (ch === '"') {
87
+ let j = i + 1;
88
+ while (j < raw.length && /[ \t\r\n]/.test(raw[j]))
89
+ j++;
90
+ const next = j < raw.length ? raw[j] : '';
91
+ if (next === '' || /[,:}\]]/.test(next)) {
92
+ result += '"';
93
+ inString = false;
94
+ }
95
+ else {
96
+ result += '\\"';
97
+ }
98
+ i++;
99
+ continue;
100
+ }
101
+ if (ch === '\n') {
102
+ result += '\\n';
103
+ i++;
104
+ continue;
105
+ }
106
+ if (ch === '\r') {
107
+ result += '\\n';
108
+ i += (i + 1 < raw.length && raw[i + 1] === '\n') ? 2 : 1;
109
+ continue;
110
+ }
111
+ if (ch === '\t') {
112
+ result += '\\t';
113
+ i++;
114
+ continue;
115
+ }
116
+ if (ch.charCodeAt(0) < 0x20) {
117
+ i++;
118
+ continue;
119
+ }
120
+ result += ch;
121
+ i++;
122
+ }
123
+ return result;
124
+ }
125
+ function parseJsonPlan(raw) {
126
+ let cleaned = raw.trim();
127
+ if (cleaned.startsWith('```')) {
128
+ cleaned = cleaned.replace(/^```(?:json|JSON)?\s*\n?/, '').replace(/\n?```\s*$/, '');
129
+ }
130
+ const firstBrace = cleaned.indexOf('{');
131
+ if (firstBrace > 0) {
132
+ cleaned = cleaned.slice(firstBrace);
133
+ }
134
+ const lastBrace = cleaned.lastIndexOf('}');
135
+ if (lastBrace >= 0 && lastBrace < cleaned.length - 1) {
136
+ cleaned = cleaned.slice(0, lastBrace + 1);
137
+ }
138
+ try {
139
+ return JSON.parse(cleaned);
140
+ }
141
+ catch (e) {
142
+ logger.debug('parseJsonPlan: direct parse failed', { error: String(e), length: cleaned.length });
143
+ }
144
+ const match = cleaned.match(/\{[\s\S]*\}/);
145
+ if (!match)
146
+ return null;
147
+ try {
148
+ return JSON.parse(match[0]);
149
+ }
150
+ catch { }
151
+ const repaired = repairLlmJson(match[0]);
152
+ try {
153
+ return JSON.parse(repaired);
154
+ }
155
+ catch (e) {
156
+ logger.debug('parseJsonPlan: repaired parse failed', { error: String(e) });
157
+ }
158
+ try {
159
+ let final = repaired;
160
+ let braces = 0, brackets = 0;
161
+ let inStr = false, esc = false;
162
+ for (const ch of final) {
163
+ if (esc) {
164
+ esc = false;
165
+ continue;
166
+ }
167
+ if (ch === '\\') {
168
+ esc = true;
169
+ continue;
170
+ }
171
+ if (ch === '"') {
172
+ inStr = !inStr;
173
+ continue;
174
+ }
175
+ if (inStr)
176
+ continue;
177
+ if (ch === '{')
178
+ braces++;
179
+ else if (ch === '}')
180
+ braces--;
181
+ else if (ch === '[')
182
+ brackets++;
183
+ else if (ch === ']')
184
+ brackets--;
185
+ }
186
+ if (inStr)
187
+ final += '"';
188
+ for (let i = 0; i < brackets; i++)
189
+ final += ']';
190
+ for (let i = 0; i < braces; i++)
191
+ final += '}';
192
+ return JSON.parse(final);
193
+ }
194
+ catch {
195
+ return null;
196
+ }
197
+ }
198
+ function extractHtml(raw) {
199
+ const trimmed = raw.trim();
200
+ if (trimmed.startsWith('<!DOCTYPE') || trimmed.startsWith('<html')) {
201
+ return trimmed;
202
+ }
203
+ const fenceMatch = trimmed.match(/```(?:html)?\s*\n([\s\S]*?)\n```/);
204
+ if (fenceMatch?.[1]) {
205
+ return fenceMatch[1].trim();
206
+ }
207
+ const docMatch = trimmed.match(/(<!DOCTYPE[\s\S]*<\/html>)/i);
208
+ if (docMatch?.[1]) {
209
+ return docMatch[1].trim();
210
+ }
211
+ return null;
212
+ }
213
+ function injectViewportCss(html, backgroundColor) {
214
+ let result = html.replace(/<meta\s+name=["']viewport["'][^>]*>/gi, '');
215
+ const bgColor = backgroundColor || '#000000';
216
+ const viewportMeta = `<meta name="viewport" content="width=2040">`;
217
+ 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}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}body>div,body>main,body>section,body>article,body>header,body>footer{max-width:none!important}body>*:nth-child(2){flex:1!important;align-items:stretch!important;align-content:stretch!important}</style>`;
218
+ const injection = viewportMeta + overrideCss;
219
+ if (result.includes('</head>')) {
220
+ result = result.replace('</head>', `${injection}</head>`);
221
+ }
222
+ else if (result.includes('<head>')) {
223
+ result = result.replace('<head>', `<head>${injection}`);
224
+ }
225
+ else if (result.includes('<html')) {
226
+ result = result.replace(/<html[^>]*>/, (match) => `${match}<head>${injection}</head>`);
227
+ }
228
+ else {
229
+ result = injection + result;
230
+ }
231
+ return result;
232
+ }
233
+ function enforceMinFontSize(html) {
234
+ return html.replace(/font-size:\s*(\d+(?:\.\d+)?)px/g, (_match, size) => {
235
+ const px = parseFloat(size);
236
+ return (px > 12 && px < 20) ? 'font-size:20px' : _match;
237
+ });
238
+ }
239
+ function escapeHtml(text) {
240
+ return text
241
+ .replace(/&/g, '&amp;')
242
+ .replace(/</g, '&lt;')
243
+ .replace(/>/g, '&gt;')
244
+ .replace(/"/g, '&quot;');
245
+ }
246
+ function buildTitleSlideHtml(design, mainTitle, subtitle, date, slideNum) {
247
+ return `<!DOCTYPE html>
248
+ <html lang="ko">
249
+ <head>
250
+ <meta charset="UTF-8">
251
+ <style>
252
+ * { margin: 0; padding: 0; box-sizing: border-box; }
253
+ html { background-color: ${design.primary_color}; }
254
+ html, body { width: 1920px; height: 1080px; overflow: hidden; }
255
+ body {
256
+ background: linear-gradient(135deg, ${design.primary_color} 0%, ${design.gradient_end} 60%, ${design.primary_color} 100%);
257
+ display: flex;
258
+ align-items: center;
259
+ justify-content: center;
260
+ position: relative;
261
+ font-family: "${design.font_title}", "Segoe UI", sans-serif;
262
+ }
263
+ .decor {
264
+ position: absolute;
265
+ border-radius: 50%;
266
+ background: rgba(255,255,255,0.03);
267
+ pointer-events: none;
268
+ }
269
+ .d1 { width: 700px; height: 700px; top: -250px; right: -150px; }
270
+ .d2 { width: 500px; height: 500px; bottom: -180px; left: -120px; }
271
+ .d3 { width: 250px; height: 250px; top: 40%; left: 8%; background: rgba(255,255,255,0.02); }
272
+ .d4 { width: 180px; height: 180px; bottom: 15%; right: 12%; background: rgba(255,255,255,0.02); }
273
+ .top-accent {
274
+ position: absolute;
275
+ top: 0; left: 0; width: 100%; height: 6px;
276
+ background: linear-gradient(90deg, transparent, ${design.accent_color}, transparent);
277
+ }
278
+ .content {
279
+ text-align: center;
280
+ z-index: 1;
281
+ max-width: 1400px;
282
+ padding: 0 60px;
283
+ }
284
+ .main-title {
285
+ font-size: 96px;
286
+ font-weight: 800;
287
+ color: #ffffff;
288
+ letter-spacing: -2px;
289
+ text-shadow: 0 6px 40px rgba(0,0,0,0.25);
290
+ line-height: 1.1;
291
+ margin-bottom: 32px;
292
+ }
293
+ .accent-bar {
294
+ width: 120px; height: 5px;
295
+ background: ${design.accent_color};
296
+ margin: 0 auto 32px;
297
+ border-radius: 3px;
298
+ box-shadow: 0 0 20px ${design.accent_color}40;
299
+ }
300
+ .subtitle {
301
+ font-size: 32px;
302
+ font-weight: 400;
303
+ color: rgba(255,255,255,0.88);
304
+ font-family: "${design.font_body}", "Malgun Gothic", sans-serif;
305
+ line-height: 1.5;
306
+ margin-bottom: 16px;
307
+ }
308
+ .date-text {
309
+ font-size: 22px;
310
+ font-weight: 300;
311
+ color: rgba(255,255,255,0.55);
312
+ font-family: "${design.font_body}", "Malgun Gothic", sans-serif;
313
+ }
314
+ .page-num {
315
+ position: absolute;
316
+ bottom: 24px; right: 44px;
317
+ font-size: 13px;
318
+ color: rgba(255,255,255,0.35);
319
+ }
320
+ </style>
321
+ </head>
322
+ <body>
323
+ <div class="top-accent"></div>
324
+ <div class="decor d1"></div>
325
+ <div class="decor d2"></div>
326
+ <div class="decor d3"></div>
327
+ <div class="decor d4"></div>
328
+ <div class="content">
329
+ <div class="main-title">${escapeHtml(mainTitle)}</div>
330
+ <div class="accent-bar"></div>
331
+ ${subtitle ? `<div class="subtitle">${escapeHtml(subtitle)}</div>` : ''}
332
+ <div class="date-text">${escapeHtml(date)}</div>
333
+ </div>
334
+ <div class="page-num">${slideNum}</div>
335
+ </body>
336
+ </html>`;
337
+ }
338
+ function buildClosingSlideHtml(design, companyName, slideNum, language) {
339
+ const thankYou = language === 'ko' ? '감사합니다' : 'Thank You';
340
+ return `<!DOCTYPE html>
341
+ <html lang="${language}">
342
+ <head>
343
+ <meta charset="UTF-8">
344
+ <style>
345
+ * { margin: 0; padding: 0; box-sizing: border-box; }
346
+ html { background-color: ${design.primary_color}; }
347
+ html, body { width: 1920px; height: 1080px; overflow: hidden; }
348
+ body {
349
+ background: linear-gradient(135deg, ${design.primary_color} 0%, ${design.gradient_end} 60%, ${design.primary_color} 100%);
350
+ display: flex;
351
+ align-items: center;
352
+ justify-content: center;
353
+ position: relative;
354
+ font-family: "${design.font_title}", "Segoe UI", sans-serif;
355
+ }
356
+ .decor {
357
+ position: absolute;
358
+ border-radius: 50%;
359
+ background: rgba(255,255,255,0.03);
360
+ pointer-events: none;
361
+ }
362
+ .d1 { width: 600px; height: 600px; top: -200px; left: -150px; }
363
+ .d2 { width: 400px; height: 400px; bottom: -120px; right: -80px; }
364
+ .bottom-accent {
365
+ position: absolute;
366
+ bottom: 0; left: 0; width: 100%; height: 6px;
367
+ background: linear-gradient(90deg, transparent, ${design.accent_color}, transparent);
368
+ }
369
+ .content {
370
+ text-align: center;
371
+ z-index: 1;
372
+ }
373
+ .thank-you {
374
+ font-size: 96px;
375
+ font-weight: 800;
376
+ color: #ffffff;
377
+ letter-spacing: -1px;
378
+ text-shadow: 0 6px 40px rgba(0,0,0,0.25);
379
+ margin-bottom: 32px;
380
+ }
381
+ .accent-bar {
382
+ width: 100px; height: 5px;
383
+ background: ${design.accent_color};
384
+ margin: 0 auto 32px;
385
+ border-radius: 3px;
386
+ box-shadow: 0 0 20px ${design.accent_color}40;
387
+ }
388
+ .company {
389
+ font-size: 36px;
390
+ font-weight: 500;
391
+ color: rgba(255,255,255,0.80);
392
+ font-family: "${design.font_body}", "Malgun Gothic", sans-serif;
393
+ }
394
+ .page-num {
395
+ position: absolute;
396
+ bottom: 24px; right: 44px;
397
+ font-size: 13px;
398
+ color: rgba(255,255,255,0.35);
399
+ }
400
+ </style>
401
+ </head>
402
+ <body>
403
+ <div class="decor d1"></div>
404
+ <div class="decor d2"></div>
405
+ <div class="bottom-accent"></div>
406
+ <div class="content">
407
+ <div class="thank-you">${escapeHtml(thankYou)}</div>
408
+ <div class="accent-bar"></div>
409
+ <div class="company">${escapeHtml(companyName)}</div>
410
+ </div>
411
+ <div class="page-num">${slideNum}</div>
412
+ </body>
413
+ </html>`;
414
+ }
415
+ function getTempDir() {
416
+ const platform = getPlatform();
417
+ if (platform === 'wsl') {
418
+ return { writePath: '/mnt/c/temp', winPath: 'C:\\temp' };
419
+ }
420
+ return { writePath: 'C:\\temp', winPath: 'C:\\temp' };
421
+ }
422
+ function ensureTempDir(writePath) {
423
+ if (!fs.existsSync(writePath)) {
424
+ fs.mkdirSync(writePath, { recursive: true });
425
+ }
426
+ }
427
+ function normalizeDesign(raw) {
428
+ return {
429
+ primary_color: raw['primary_color'] || DEFAULT_DESIGN.primary_color,
430
+ accent_color: raw['accent_color'] || DEFAULT_DESIGN.accent_color,
431
+ background_color: raw['background_color'] || DEFAULT_DESIGN.background_color,
432
+ text_color: raw['text_color'] || DEFAULT_DESIGN.text_color,
433
+ accent_light: raw['accent_light'] || DEFAULT_DESIGN.accent_light,
434
+ gradient_end: raw['gradient_end'] || DEFAULT_DESIGN.gradient_end,
435
+ font_title: raw['font_title'] || DEFAULT_DESIGN.font_title,
436
+ font_body: raw['font_body'] || DEFAULT_DESIGN.font_body,
437
+ mood: raw['mood'] || DEFAULT_DESIGN.mood,
438
+ design_notes: raw['design_notes'] || DEFAULT_DESIGN.design_notes,
439
+ };
440
+ }
441
+ async function runStructured(llmClient, instruction) {
442
+ const startTime = Date.now();
443
+ const phaseLogger = getSubAgentPhaseLogger();
444
+ const toolCallLogger = getSubAgentToolCallLogger();
445
+ let totalToolCalls = 0;
446
+ const timestamp = Date.now();
447
+ logger.enter('PPT-Create.runStructured');
448
+ const hasKorean = /[\uac00-\ud7af\u1100-\u11ff]/.test(instruction);
449
+ const language = hasKorean ? 'ko' : 'en';
450
+ if (phaseLogger)
451
+ phaseLogger('powerpoint-create', 'enhancement', 'Generating creative guidance...');
452
+ let guidance = '';
453
+ try {
454
+ const enhRes = await llmClient.chatCompletion({
455
+ messages: [
456
+ { role: 'system', content: PPT_CREATE_ENHANCEMENT_PROMPT },
457
+ { role: 'user', content: instruction },
458
+ ],
459
+ temperature: 0.7,
460
+ max_tokens: 2000,
461
+ });
462
+ const enhMsg = enhRes.choices[0]?.message;
463
+ guidance = enhMsg ? extractContent(enhMsg) : '';
464
+ if (guidance.length < 500) {
465
+ logger.warn('PPT enhancement too short, retrying', { length: guidance.length });
466
+ const retryEnhRes = await llmClient.chatCompletion({
467
+ messages: [
468
+ { role: 'system', content: PPT_CREATE_ENHANCEMENT_PROMPT },
469
+ { role: 'user', content: `${instruction}\n\nIMPORTANT: Produce a COMPLETE, DETAILED design system and content plan. Include ALL 7 sections: DOCUMENT_TYPE, AUDIENCE, MOOD, COLOR PALETTE (6 hex values), FONTS, SLIDE PLAN (10-15 slides), DESIGN NOTES. Do NOT stop early.` },
470
+ ],
471
+ temperature: 0.7,
472
+ max_tokens: 2000,
473
+ });
474
+ const retryMsg = retryEnhRes.choices[0]?.message;
475
+ const retryGuidance = retryMsg ? extractContent(retryMsg) : '';
476
+ if (retryGuidance.length > guidance.length) {
477
+ guidance = retryGuidance;
478
+ }
479
+ }
480
+ if (phaseLogger)
481
+ phaseLogger('powerpoint-create', 'enhancement', `Done (${guidance.length} chars)`);
482
+ }
483
+ catch (e) {
484
+ logger.warn('PPT enhancement failed, proceeding without', { error: String(e) });
485
+ }
486
+ const enhancedInstruction = guidance
487
+ ? `${instruction}\n\n═══ CREATIVE GUIDANCE ═══\n${guidance}\n═══ END GUIDANCE ═══`
488
+ : instruction;
489
+ if (phaseLogger)
490
+ phaseLogger('powerpoint-create', 'planning', 'Generating JSON plan...');
491
+ let plan = null;
492
+ try {
493
+ const planRes = await llmClient.chatCompletion({
494
+ messages: [
495
+ { role: 'system', content: PPT_STRUCTURED_PLANNING_PROMPT },
496
+ { role: 'user', content: enhancedInstruction },
497
+ ],
498
+ temperature: 0.4,
499
+ max_tokens: 8000,
500
+ });
501
+ const planMsg = planRes.choices[0]?.message;
502
+ const finishReason = planRes.choices[0]?.finish_reason;
503
+ const rawPlan = planMsg ? extractContent(planMsg) : '';
504
+ if (finishReason === 'length') {
505
+ logger.warn('PPT planning response was truncated (finish_reason=length)');
506
+ }
507
+ logger.debug('PPT planning raw response', { length: rawPlan.length, finishReason, first200: rawPlan.slice(0, 200) });
508
+ plan = rawPlan ? parseJsonPlan(rawPlan) : null;
509
+ if (plan) {
510
+ plan.design = normalizeDesign(plan.design);
511
+ const validationError = validatePlan(plan);
512
+ if (validationError) {
513
+ logger.warn('PPT plan validation failed', { error: validationError });
514
+ if (phaseLogger)
515
+ phaseLogger('powerpoint-create', 'planning', `Validation failed: ${validationError}. Retrying...`);
516
+ const retryRes = await llmClient.chatCompletion({
517
+ messages: [
518
+ { role: 'system', content: PPT_STRUCTURED_PLANNING_PROMPT },
519
+ { role: 'user', content: enhancedInstruction },
520
+ ],
521
+ temperature: 0.2,
522
+ max_tokens: 8000,
523
+ });
524
+ const retryMsg = retryRes.choices[0]?.message;
525
+ const retryRaw = retryMsg ? extractContent(retryMsg) : '';
526
+ const retryPlan = retryRaw ? parseJsonPlan(retryRaw) : null;
527
+ if (retryPlan) {
528
+ retryPlan.design = normalizeDesign(retryPlan.design);
529
+ const retryError = validatePlan(retryPlan);
530
+ if (!retryError) {
531
+ plan = retryPlan;
532
+ if (phaseLogger)
533
+ phaseLogger('powerpoint-create', 'planning', `Retry succeeded (${plan.slides.length} slides)`);
534
+ }
535
+ else {
536
+ plan = null;
537
+ }
538
+ }
539
+ else {
540
+ plan = null;
541
+ }
542
+ }
543
+ else {
544
+ if (phaseLogger)
545
+ phaseLogger('powerpoint-create', 'planning', `Done (${plan.slides.length} slides)`);
546
+ }
547
+ }
548
+ else {
549
+ logger.warn('PPT JSON plan parsing failed');
550
+ if (phaseLogger)
551
+ phaseLogger('powerpoint-create', 'planning', 'JSON parsing failed. Falling back.');
552
+ }
553
+ }
554
+ catch (e) {
555
+ logger.warn('PPT planning failed', { error: String(e) });
556
+ if (phaseLogger)
557
+ phaseLogger('powerpoint-create', 'planning', 'Planning error. Falling back.');
558
+ }
559
+ if (!plan) {
560
+ logger.info('PPT falling back to SubAgent.run()');
561
+ if (phaseLogger)
562
+ phaseLogger('powerpoint-create', 'fallback', 'Using SubAgent execution loop');
563
+ const agent = new SubAgent(llmClient, 'powerpoint-create', POWERPOINT_CREATE_TOOLS, PPT_CREATE_SYSTEM_PROMPT, {
564
+ maxIterations: 30,
565
+ planningPrompt: PPT_CREATE_PLANNING_PROMPT,
566
+ planningMaxTokens: 2000,
567
+ enhancementPrompt: PPT_CREATE_ENHANCEMENT_PROMPT,
568
+ enhancementMaxTokens: 800,
569
+ minToolCallsBeforeComplete: 14,
570
+ });
571
+ return agent.run(instruction);
572
+ }
573
+ if (plan.slides.length > 15) {
574
+ logger.warn(`PPT plan has ${plan.slides.length} slides, capping to 15`);
575
+ const firstSlide = plan.slides[0];
576
+ const lastSlide = plan.slides[plan.slides.length - 1];
577
+ const contentSlides = plan.slides.slice(1, -1).slice(0, 13);
578
+ plan.slides = [firstSlide, ...contentSlides, lastSlide];
579
+ if (phaseLogger)
580
+ phaseLogger('powerpoint-create', 'planning', `Capped to ${plan.slides.length} slides`);
581
+ }
582
+ if (phaseLogger)
583
+ phaseLogger('powerpoint-create', 'execution', 'Starting HTML rendering pipeline...');
584
+ const { writePath: tempWritePath, winPath: tempWinPath } = getTempDir();
585
+ ensureTempDir(tempWritePath);
586
+ const pathMatch = instruction.match(/([A-Za-z]:\\[^\s,]+\.pptx|\/[^\s,]+\.pptx)/i);
587
+ const savePath = pathMatch ? pathMatch[1] : undefined;
588
+ const kstNow = new Date(Date.now() + 9 * 60 * 60 * 1000);
589
+ const kstDate = `${kstNow.getUTCFullYear()}년 ${kstNow.getUTCMonth() + 1}월`;
590
+ const titleSlidePlan = plan.slides.find(s => s.type === 'title');
591
+ const rawTitleText = titleSlidePlan?.title || '';
592
+ const titleSeps = [' - ', ' – ', ' — ', ': ', ' | '];
593
+ let companyName = rawTitleText;
594
+ let titleSubtitle = '';
595
+ for (const sep of titleSeps) {
596
+ const idx = rawTitleText.indexOf(sep);
597
+ if (idx > 0) {
598
+ companyName = rawTitleText.slice(0, idx).trim();
599
+ titleSubtitle = rawTitleText.slice(idx + sep.length).trim();
600
+ break;
601
+ }
602
+ }
603
+ if (!titleSubtitle && titleSlidePlan) {
604
+ titleSubtitle = ((titleSlidePlan.content_direction || '').split('\n')[0] || '').trim().slice(0, 120);
605
+ }
606
+ if (/로고|슬로건|연락처|contact|logo|placeholder/i.test(titleSubtitle)) {
607
+ titleSubtitle = '';
608
+ }
609
+ const createResult = await powerpointClient.powerpointCreate();
610
+ totalToolCalls++;
611
+ if (toolCallLogger)
612
+ toolCallLogger('powerpoint-create', 'powerpoint_create', {}, createResult.success ? 'Created' : createResult['error'] || '', createResult.success, 0, totalToolCalls);
613
+ if (!createResult.success) {
614
+ return { success: false, error: `Failed to create presentation: ${createResult['error']}` };
615
+ }
616
+ const builtSlides = [];
617
+ let failCount = 0;
618
+ const tempFiles = [];
619
+ for (let i = 0; i < plan.slides.length; i++) {
620
+ const slidePlan = plan.slides[i];
621
+ const slideNum = i + 1;
622
+ if (failCount >= 3) {
623
+ logger.warn('Too many slide failures, stopping');
624
+ break;
625
+ }
626
+ if (phaseLogger)
627
+ phaseLogger('powerpoint-create', 'execution', `Rendering slide ${slideNum}/${plan.slides.length}: ${slidePlan.title}`);
628
+ let html = null;
629
+ if (slidePlan.type === 'title') {
630
+ html = buildTitleSlideHtml(plan.design, companyName, titleSubtitle, kstDate, slideNum);
631
+ }
632
+ else if (slidePlan.type === 'closing') {
633
+ html = buildClosingSlideHtml(plan.design, companyName, slideNum, language);
634
+ }
635
+ else {
636
+ const htmlPrompt = buildSlideHtmlPrompt(slidePlan.title, slidePlan.content_direction || '', plan.design, i, plan.slides.length, language);
637
+ try {
638
+ const htmlRes = await llmClient.chatCompletion({
639
+ messages: [
640
+ { role: 'system', content: htmlPrompt },
641
+ { role: 'user', content: 'Generate the HTML slide now.' },
642
+ ],
643
+ temperature: 0.3,
644
+ max_tokens: 8000,
645
+ });
646
+ const htmlMsg = htmlRes.choices[0]?.message;
647
+ const rawHtml = htmlMsg ? extractContent(htmlMsg) : '';
648
+ html = extractHtml(rawHtml);
649
+ if (!html && rawHtml.length > 100) {
650
+ logger.warn(`Slide ${slideNum}: HTML extraction failed, retrying`);
651
+ const retryRes = await llmClient.chatCompletion({
652
+ messages: [
653
+ { role: 'system', content: htmlPrompt },
654
+ { role: 'user', content: 'Generate the complete HTML document. Start with <!DOCTYPE html> and end with </html>. No markdown fences.' },
655
+ ],
656
+ temperature: 0.2,
657
+ max_tokens: 4000,
658
+ });
659
+ const retryMsg = retryRes.choices[0]?.message;
660
+ const retryRaw = retryMsg ? extractContent(retryMsg) : '';
661
+ html = extractHtml(retryRaw);
662
+ }
663
+ }
664
+ catch (e) {
665
+ logger.warn(`Slide ${slideNum}: LLM call failed: ${e}`);
666
+ }
667
+ }
668
+ if (!html) {
669
+ logger.warn(`Slide ${slideNum}: Failed to generate HTML, skipping`);
670
+ failCount++;
671
+ continue;
672
+ }
673
+ const htmlFileName = `hanseol_slide_${slideNum}_${timestamp}.html`;
674
+ const pngFileName = `hanseol_slide_${slideNum}_${timestamp}.png`;
675
+ const htmlWritePath = path.join(tempWritePath, htmlFileName);
676
+ const pngWritePath = path.join(tempWritePath, pngFileName);
677
+ const htmlWinPath = `${tempWinPath}\\${htmlFileName}`;
678
+ const pngWinPath = `${tempWinPath}\\${pngFileName}`;
679
+ try {
680
+ fs.writeFileSync(htmlWritePath, enforceMinFontSize(injectViewportCss(html, plan.design.background_color)), 'utf-8');
681
+ tempFiles.push(htmlWritePath);
682
+ }
683
+ catch (e) {
684
+ logger.warn(`Slide ${slideNum}: Failed to write HTML file: ${e}`);
685
+ failCount++;
686
+ continue;
687
+ }
688
+ let renderSuccess = false;
689
+ try {
690
+ const renderResult = await powerpointClient.renderHtmlToImage(htmlWinPath, pngWinPath);
691
+ totalToolCalls++;
692
+ renderSuccess = renderResult.success;
693
+ if (!renderSuccess) {
694
+ await new Promise(r => setTimeout(r, 2000));
695
+ const retryRender = await powerpointClient.renderHtmlToImage(htmlWinPath, pngWinPath);
696
+ totalToolCalls++;
697
+ renderSuccess = retryRender.success;
698
+ }
699
+ }
700
+ catch (e) {
701
+ logger.warn(`Slide ${slideNum}: Edge screenshot failed: ${e}`);
702
+ try {
703
+ await new Promise(r => setTimeout(r, 2000));
704
+ const retryRender = await powerpointClient.renderHtmlToImage(htmlWinPath, pngWinPath);
705
+ totalToolCalls++;
706
+ renderSuccess = retryRender.success;
707
+ }
708
+ catch {
709
+ renderSuccess = false;
710
+ }
711
+ }
712
+ try {
713
+ fs.unlinkSync(htmlWritePath);
714
+ }
715
+ catch { }
716
+ if (!renderSuccess) {
717
+ logger.warn(`Slide ${slideNum}: Screenshot rendering failed, skipping`);
718
+ failCount++;
719
+ continue;
720
+ }
721
+ try {
722
+ const pngStats = fs.statSync(pngWritePath);
723
+ if (pngStats.size < 15000) {
724
+ logger.warn(`Slide ${slideNum}: Screenshot too small (${pngStats.size} bytes), likely blank — skipping`);
725
+ failCount++;
726
+ try {
727
+ fs.unlinkSync(pngWritePath);
728
+ }
729
+ catch { }
730
+ continue;
731
+ }
732
+ }
733
+ catch { }
734
+ tempFiles.push(pngWritePath);
735
+ const addResult = await powerpointClient.powerpointAddSlide(7);
736
+ totalToolCalls++;
737
+ if (!addResult.success) {
738
+ logger.warn(`Slide ${slideNum}: Failed to add blank slide`);
739
+ failCount++;
740
+ continue;
741
+ }
742
+ const bgResult = await powerpointClient.powerpointAddFullSlideImage(slideNum, pngWinPath);
743
+ totalToolCalls++;
744
+ if (toolCallLogger)
745
+ toolCallLogger('powerpoint-create', 'addFullSlideImage', { slideNum, imagePath: pngWinPath }, bgResult.success ? 'OK' : 'Failed', bgResult.success, slideNum, totalToolCalls);
746
+ if (bgResult.success) {
747
+ builtSlides.push(`Slide ${slideNum}: ${slidePlan.title} (${slidePlan.type})`);
748
+ try {
749
+ await powerpointClient.powerpointAddNote(slideNum, html);
750
+ }
751
+ catch { }
752
+ }
753
+ else {
754
+ logger.warn(`Slide ${slideNum}: Failed to set background: ${JSON.stringify(bgResult)}`);
755
+ failCount++;
756
+ }
757
+ }
758
+ if (builtSlides.length > 0) {
759
+ try {
760
+ const slideCountResult = await powerpointClient.powerpointGetSlideCount();
761
+ const totalSlidesInPpt = slideCountResult['slide_count'] || 0;
762
+ if (totalSlidesInPpt > builtSlides.length) {
763
+ logger.warn(`PPT has ${totalSlidesInPpt} slides but only ${builtSlides.length} were rendered — deleting ${totalSlidesInPpt - builtSlides.length} trailing blanks`);
764
+ for (let d = totalSlidesInPpt; d > builtSlides.length; d--) {
765
+ await powerpointClient.powerpointDeleteSlide(d);
766
+ totalToolCalls++;
767
+ }
768
+ }
769
+ }
770
+ catch (e) {
771
+ logger.warn(`Failed to clean up trailing slides: ${e}`);
772
+ }
773
+ }
774
+ if (builtSlides.length > 0) {
775
+ let saveResult = await powerpointClient.powerpointSave(savePath);
776
+ totalToolCalls++;
777
+ if (toolCallLogger)
778
+ toolCallLogger('powerpoint-create', 'powerpoint_save', { path: savePath }, saveResult.success ? (saveResult['path'] || 'OK') : (saveResult.error || 'Failed'), saveResult.success, 0, totalToolCalls);
779
+ if (!saveResult.success && savePath) {
780
+ const fallbackPath = 'C:\\temp\\presentation.pptx';
781
+ saveResult = await powerpointClient.powerpointSave(fallbackPath);
782
+ totalToolCalls++;
783
+ if (toolCallLogger)
784
+ toolCallLogger('powerpoint-create', 'powerpoint_save', { path: fallbackPath }, saveResult.success ? (saveResult['path'] || 'OK') : (saveResult.error || 'Failed'), saveResult.success, 0, totalToolCalls);
785
+ }
786
+ }
787
+ for (const tempFile of tempFiles) {
788
+ try {
789
+ fs.unlinkSync(tempFile);
790
+ }
791
+ catch { }
792
+ }
793
+ const duration = Date.now() - startTime;
794
+ const summary = `Presentation created with ${builtSlides.length} slides (HTML rendering, ${plan.design.mood}):\n${builtSlides.join('\n')}`;
795
+ logger.exit('PPT-Create.runStructured', { slideCount: builtSlides.length, totalToolCalls, duration });
796
+ return {
797
+ success: builtSlides.length > 0,
798
+ result: summary,
799
+ metadata: { iterations: plan.slides.length, toolCalls: totalToolCalls, duration },
800
+ };
801
+ }
802
+ export function createPowerPointCreateRequestTool() {
803
+ return {
804
+ definition: {
805
+ type: 'function',
806
+ function: {
807
+ name: 'powerpoint_create_agent',
808
+ 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.',
809
+ parameters: {
810
+ type: 'object',
811
+ properties: {
812
+ instruction: {
813
+ type: 'string',
814
+ description: 'Detailed instruction for creating a new presentation. Include: topic/title, desired content, design preferences, and save path. The agent autonomously creates a professional presentation with title, content, and closing slides.',
815
+ },
816
+ },
817
+ required: ['instruction'],
818
+ },
819
+ },
820
+ },
821
+ execute: async (args, llmClient) => {
822
+ return runStructured(llmClient, args['instruction']);
823
+ },
824
+ categories: ['llm-agent'],
825
+ requiresSubLLM: true,
826
+ };
827
+ }
828
+ //# sourceMappingURL=powerpoint-create-agent.js.map