// pptx-workflow.js — PPTX 마법사 사이드바 워크플로(이미지 캡처→아웃라인→생성).
// extracted from app.js (v4.3.19-3 split).
let _pptxImages = []; // {url, thumb, label}
let _pptxOutline = null; // parsed pptx-outline JSON
let _pptxOutlineEdits = {}; // {index: {title, bullets}}
function setPptxSbTab(tab) {
['setup','images','outline'].forEach(t => {
document.getElementById('psb-tab-' + t).classList.toggle('active', t === tab);
const el = document.getElementById('psb-' + t);
if (el) el.style.display = t === tab ? (t === 'outline' ? 'flex' : (t === 'images' ? 'block' : 'flex')) : 'none';
});
}
function pptxSetStatus(msg) {
const el = document.getElementById('psb-status');
if (el) el.textContent = msg;
}
function pptxStartWorkflow() {
const topic = (document.getElementById('psb-topic').value || '').trim();
const pmcidsRaw = (document.getElementById('psb-pmcids').value || '').trim();
const template = document.getElementById('psb-template').value || 'dark';
const slideCount = parseInt(document.getElementById('psb-slide-count').value) || 0;
if (!topic) { pptxSetStatus('주제를 입력하세요.'); return; }
const pmcids = pmcidsRaw
.split(/[\n,]+/)
.map(s => s.trim().replace(/^PMC/i, 'PMC'))
.filter(Boolean);
_pptxImages = [];
_pptxOutline = null;
_pptxOutlineEdits = {};
pptxRenderImageGrid();
const cardsEl = document.getElementById('psb-outline-cards');
if (cardsEl) cardsEl.innerHTML = '
아웃라인 생성 중...
';
const genBtn = document.getElementById('psb-gen-btn');
if (genBtn) { genBtn.disabled = true; genBtn.style.opacity = '.5'; }
// Build workflow message
const pmcLine = pmcids.length
? `\nPMCIDs: ${pmcids.join(', ')}`
: '';
const slideLine = slideCount ? `\n슬라이드 수: ${slideCount}장` : '';
const msg = `[PPTX 워크플로 시작]\n주제: ${topic}${pmcLine}\n템플릿: ${template}${slideLine}\n\n아래 순서로 진행해주세요:\n1. 작업 폴더 생성 (주제 기반)\n2. PDF 다운로드 (pubmed_fulltext, 위 PMCIDs)\n3. 이미지 추출 (pdf_extract_images, 각 PDF)\n4. 소형 이미지 필터링 (python_eval)\n5. 슬라이드 구성을 \`\`\`pptx-outline 블록으로 출력 (JSON 형식)\n6. 확인 대기 (create_presentation은 아직 호출하지 마세요)`;
pptxSetStatus('워크플로 시작...');
setSidebarTab('pptx');
setPptxSbTab('setup');
// Send via main chat
const input = document.getElementById('chat-input');
if (input) {
input.value = msg;
sendChat();
}
}
function pptxCaptureImages(toolResultText) {
// Parse image links from pdf_extract_images output: [label](/api/files/...)
const re = /\[([^\]]*)\]\((\/api\/files\/[^)]+\.(?:png|jpg|jpeg|gif|webp))\)/gi;
let m;
while ((m = re.exec(toolResultText)) !== null) {
const label = m[1];
const url = m[2];
if (!_pptxImages.some(i => i.url === url)) {
_pptxImages.push({ url, label });
}
}
if (_pptxImages.length) {
pptxRenderImageGrid();
const countEl = document.getElementById('psb-img-count');
if (countEl) countEl.textContent = `(${_pptxImages.length})`;
// Auto-switch to images tab if PPTX tab is active
if (sidebarTab === 'pptx') setPptxSbTab('images');
pptxSetStatus(`이미지 ${_pptxImages.length}개 추출됨`);
}
}
function pptxRenderImageGrid() {
const grid = document.getElementById('psb-image-grid');
if (!grid) return;
if (!_pptxImages.length) {
grid.innerHTML = '이미지 추출 후 표시됩니다
';
return;
}
grid.innerHTML = _pptxImages.map((img, idx) => `
${escHtml(img.label)}
`).join('');
}
function pptxImageClick(idx) {
const img = _pptxImages[idx];
if (!img) return;
window.open(img.url, '_blank', 'noopener');
}
function pptxSetOutline(data) {
_pptxOutline = data;
_pptxOutlineEdits = {};
pptxRenderOutlineCards();
const genBtn = document.getElementById('psb-gen-btn');
if (genBtn) { genBtn.disabled = false; genBtn.style.opacity = '1'; }
if (sidebarTab === 'pptx') setPptxSbTab('outline');
pptxSetStatus(`아웃라인 준비 (${(data.slides || []).length}장)`);
}
const SLIDE_TYPE_ICON = { title:'🏷', section:'📂', content:'📝', image:'🖼', table:'📊', chart:'📈', timeline:'⏳', compare:'⚖' };
function pptxRenderOutlineCards() {
const container = document.getElementById('psb-outline-cards');
if (!container || !_pptxOutline) return;
const slides = _pptxOutline.slides || [];
if (!slides.length) {
container.innerHTML = '슬라이드가 없습니다
';
return;
}
container.innerHTML = slides.map((s, i) => {
const icon = SLIDE_TYPE_ICON[s.type] || '📄';
const imgThumb = s.image_url
? `
`
: `🖼
`;
const bulletsPreview = Array.isArray(s.bullets) && s.bullets.length
? `${s.bullets.slice(0,2).map(b => `• ${b}`).join('
')}
`
: '';
return `
${i+1}
${imgThumb}
${icon} ${escHtml(s.title || '(제목 없음)')}
${s.type}
${bulletsPreview}
${Array.isArray(s.bullets) ? `` : ''}
`;
}).join('');
}
function pptxToggleEdit(idx) {
const el = document.getElementById('psb-edit-' + idx);
if (el) el.style.display = el.style.display === 'none' ? 'block' : 'none';
}
function pptxEditTitle(idx, val) {
if (!_pptxOutlineEdits[idx]) _pptxOutlineEdits[idx] = {};
_pptxOutlineEdits[idx].title = val;
}
function pptxEditBullets(idx, val) {
if (!_pptxOutlineEdits[idx]) _pptxOutlineEdits[idx] = {};
_pptxOutlineEdits[idx].bullets = val.split(/\n\n+/).map(g => g.replace(/^\n+|\n+$/g, '')).filter(g => g.trim() !== '');
}
function pptxGenerate() {
if (!_pptxOutline) return;
// Apply edits to outline
const slides = (_pptxOutline.slides || []).map((s, i) => {
const ed = _pptxOutlineEdits[i];
if (!ed) return s;
return Object.assign({}, s, ed.title !== undefined ? { title: ed.title } : {}, ed.bullets !== undefined ? { bullets: ed.bullets } : {});
});
const edited = Object.assign({}, _pptxOutline, { slides });
const hasEdits = Object.keys(_pptxOutlineEdits).length > 0;
const msg = hasEdits
? `아웃라인을 수정했습니다. 수정된 아웃라인으로 create_presentation을 호출해주세요:\n\`\`\`json\n${JSON.stringify(edited, null, 2)}\n\`\`\``
: `아웃라인 확인했습니다. create_presentation으로 슬라이드를 생성해주세요.`;
const genBtn = document.getElementById('psb-gen-btn');
if (genBtn) { genBtn.disabled = true; genBtn.style.opacity = '.5'; }
pptxSetStatus('생성 중...');
const input = document.getElementById('chat-input');
if (input) { input.value = msg; sendChat(); }
}
function pptxResetWorkflow() {
_pptxImages = [];
_pptxOutline = null;
_pptxOutlineEdits = {};
pptxRenderImageGrid();
const cardsEl = document.getElementById('psb-outline-cards');
if (cardsEl) cardsEl.innerHTML = '아웃라인 생성 후 표시됩니다
';
const genBtn = document.getElementById('psb-gen-btn');
if (genBtn) { genBtn.disabled = true; genBtn.style.opacity = '.5'; }
const countEl = document.getElementById('psb-img-count');
if (countEl) countEl.textContent = '';
pptxSetStatus('');
setPptxSbTab('setup');
}