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.
- package/dist/agents/office/powerpoint-create-agent.d.ts.map +1 -1
- package/dist/agents/office/powerpoint-create-agent.js +284 -166
- package/dist/agents/office/powerpoint-create-agent.js.map +1 -1
- package/dist/agents/office/powerpoint-create-prompts.d.ts +17 -4
- package/dist/agents/office/powerpoint-create-prompts.d.ts.map +1 -1
- package/dist/agents/office/powerpoint-create-prompts.js +173 -331
- package/dist/agents/office/powerpoint-create-prompts.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/tools/office/powerpoint-client.d.ts +1 -0
- package/dist/tools/office/powerpoint-client.d.ts.map +1 -1
- package/dist/tools/office/powerpoint-client.js +44 -0
- package/dist/tools/office/powerpoint-client.js.map +1 -1
- package/dist/tools/office/powerpoint-tools/layout-builders.d.ts.map +1 -1
- package/dist/tools/office/powerpoint-tools/layout-builders.js +31 -17
- package/dist/tools/office/powerpoint-tools/layout-builders.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"powerpoint-create-agent.d.ts","sourceRoot":"","sources":["../../../src/agents/office/powerpoint-create-agent.ts"],"names":[],"mappings":"
|
|
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 {
|
|
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 {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
'
|
|
10
|
-
'
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
32
|
-
return 'Missing design
|
|
33
|
-
|
|
34
|
-
|
|
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]?.
|
|
44
|
-
return 'First slide must be
|
|
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]?.
|
|
47
|
-
return 'Last slide must be
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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}.
|
|
152
|
-
|
|
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
|
|
186
|
-
const
|
|
187
|
-
|
|
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
|
|
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', {
|
|
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
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
continue;
|
|
303
|
+
if (failCount >= 3) {
|
|
304
|
+
logger.warn('Too many slide failures, stopping');
|
|
305
|
+
break;
|
|
212
306
|
}
|
|
213
|
-
if (
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
|
256
|
-
|
|
257
|
-
|
|
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
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
276
|
-
|
|
372
|
+
}
|
|
373
|
+
catch (e) {
|
|
374
|
+
logger.warn(`Slide ${slideNum}: Edge screenshot failed: ${e}`);
|
|
277
375
|
try {
|
|
278
|
-
|
|
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
|
-
|
|
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
|
-
|
|
301
|
-
|
|
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
|
-
|
|
305
|
-
|
|
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
|
|
310
|
-
|
|
311
|
-
|
|
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',
|
|
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 (
|
|
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:
|
|
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
|
|
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: {
|