v1.0.5
This commit is contained in:
+69
-32
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user