This commit is contained in:
kim
2026-04-29 18:03:48 +09:00
parent 4324985508
commit 1106553b81
19 changed files with 74 additions and 274 deletions
+69 -32
View File
@@ -529,45 +529,66 @@ export const pptxTool: import('./registry.js').Tool = {
if (!spec.template && config.ppt?.template) spec.template = config.ppt.template;
if (!spec.default_skin && config.ppt?.skin) spec.default_skin = config.ppt.skin;
// Fix deprecated/defunct image URLs before sending to Python
// Fix deprecated/defunct image URLs before downloading
for (const slide of spec.slides) {
if (slide.image_url) {
// source.unsplash.com is shut down — redirect to images.unsplash.com
slide.image_url = slide.image_url.replace(/^https?:\/\/source\.unsplash\.com/i, 'https://images.unsplash.com');
}
}
// Resolve image_search: download images by keyword before creating slides
const slidesNeedingImages = spec.slides.filter(s => s.image_search && !s.image_path && !s.image_url);
if (slidesNeedingImages.length > 0) {
const projectSlug = (spec.title || 'presentation').replace(/[^a-zA-Z0-9가-힣_\-]/g, '_').toLowerCase().slice(0, 60);
const projectDir = path.join(workspacePath, projectSlug);
console.log(`[pptx] image_search: resolving ${slidesNeedingImages.length} slides, projectDir=${projectDir}`);
if (!fs.existsSync(projectDir)) {
fs.mkdirSync(projectDir, { recursive: true });
}
for (const slide of spec.slides) {
if (!slide.image_search || slide.image_path || slide.image_url) continue;
const idx = spec.slides.indexOf(slide);
const sendSSE = (args as any)?._sendSSE as SSESender | undefined;
const localPath = await searchAndDownloadImage(slide.image_search, projectDir, idx, sendSSE);
console.log(`[pptx] image_search: slide ${idx + 1} keyword="${slide.image_search}" -> localPath="${localPath}"`);
// ── Phase 1: Create project directory ──
const projectSlug = (spec.title || 'presentation').replace(/[^a-zA-Z0-9가-힣_\-]/g, '_').toLowerCase().slice(0, 60);
const projectDir = path.join(workspacePath, projectSlug);
if (!fs.existsSync(projectDir)) {
fs.mkdirSync(projectDir, { recursive: true });
}
console.log(`[pptx] project dir: ${projectDir}`);
// ── Phase 2: Download all images (image_search + image_url) ──
const sendSSE = (args as any)?._sendSSE as SSESender | undefined;
for (let i = 0; i < spec.slides.length; i++) {
const slide = spec.slides[i];
// image_search takes priority
if (slide.image_search && !slide.image_path) {
const localPath = await searchAndDownloadImage(slide.image_search, projectDir, i, sendSSE);
console.log(`[pptx] slide ${i + 1} image_search="${slide.image_search}" -> "${localPath}"`);
if (localPath) {
const fullPath = path.join(projectDir, localPath);
console.log(`[pptx] image_search: file exists=${fs.existsSync(fullPath)} size=${fs.statSync(fullPath).size} at ${fullPath}`);
slide.image_path = localPath;
}
delete slide.image_search;
// image_search overrides image_url
if (slide.image_url) delete slide.image_url;
continue;
}
// image_url as fallback
if (slide.image_url && !slide.image_path) {
const ext = slide.image_url.match(/\.(png|webp|gif)/i) ? '.png' : '.jpg';
const fname = `slide${i + 1}_image${ext}`;
const dest = path.join(projectDir, fname);
sendSSE?.('info', { message: `📥 Slide ${i + 1}: downloading image URL...` });
if (await downloadImageByUrl(slide.image_url, dest)) {
slide.image_path = fname;
console.log(`[pptx] slide ${i + 1} image_url downloaded -> ${fname}`);
} else {
console.log(`[pptx] slide ${i + 1} image_url failed: ${slide.image_url.slice(0, 80)}`);
sendSSE?.('info', { message: `⚠️ Slide ${i + 1}: image URL download failed` });
}
delete slide.image_url;
}
}
// Debug: log final slide specs for image slides
for (const [i, s] of spec.slides.entries()) {
if (s.image_path || s.image_url || s.image_search) {
console.log(`[pptx] slide ${i + 1}: type=${s.type} image_path="${s.image_path}" image_url="${s.image_url}"`);
if (s.image_path) {
const full = path.join(projectDir, s.image_path);
console.log(`[pptx] slide ${i + 1}: type=${s.type} image_path="${s.image_path}" exists=${fs.existsSync(full)}`);
}
}
// ── Phase 3: Generate PPTX ──
return await generateWithPython(spec, workspacePath);
},
};
@@ -646,27 +667,43 @@ export const editPptxTool: import('./registry.js').Tool = {
return { success: false, error: 'spec.slides must be a non-empty array' };
}
// Fix deprecated/defunct image URLs
// Fix deprecated/defunct image URLs before downloading
for (const slide of spec.slides) {
if (slide.image_url) {
slide.image_url = slide.image_url.replace(/^https?:\/\/source\.unsplash\.com/i, 'https://images.unsplash.com');
}
}
// Resolve image_search: download images by keyword before creating slides
const slidesNeedingImages = spec.slides.filter(s => s.image_search && !s.image_path && !s.image_url);
if (slidesNeedingImages.length > 0) {
// For edit, put downloads in the same directory as the existing PPTX
const editProjectDir = path.dirname(absPath);
for (const slide of spec.slides) {
if (!slide.image_search || slide.image_path || slide.image_url) continue;
const idx = spec.slides.indexOf(slide);
const sendSSE = (args as any)?._sendSSE as SSESender | undefined;
const localPath = await searchAndDownloadImage(slide.image_search, editProjectDir, idx, sendSSE);
// ── Download all images into the PPTX directory ──
const editProjectDir = path.dirname(absPath);
const sendSSE = (args as any)?._sendSSE as SSESender | undefined;
for (let i = 0; i < spec.slides.length; i++) {
const slide = spec.slides[i];
// image_search takes priority
if (slide.image_search && !slide.image_path) {
const localPath = await searchAndDownloadImage(slide.image_search, editProjectDir, i, sendSSE);
if (localPath) {
slide.image_path = localPath;
}
delete slide.image_search;
if (slide.image_url) delete slide.image_url;
continue;
}
// image_url as fallback
if (slide.image_url && !slide.image_path) {
const ext = slide.image_url.match(/\.(png|webp|gif)/i) ? '.png' : '.jpg';
const fname = `slide${i + 1}_image${ext}`;
const dest = path.join(editProjectDir, fname);
sendSSE?.('info', { message: `📥 Slide ${i + 1}: downloading image URL...` });
if (await downloadImageByUrl(slide.image_url, dest)) {
slide.image_path = fname;
} else {
sendSSE?.('info', { message: `⚠️ Slide ${i + 1}: image URL download failed` });
}
delete slide.image_url;
}
}