// Adapter โ€” converts /api/state into the mockup's window.PALETTE_DATA shape. // The v2 stages were written against a static seed; we keep the same shape // but populate from live backend data so all the mockup screens just work. const V2_TIER_MAP = { literal:'LITERAL', sensory:'SENSORY', emotion:'EMOTION', universal:'UNIVERSAL', metaphor:'METAPHOR', }; const v2NormalizeKind = (kindOrName) => { const k = (kindOrName||'').toLowerCase(); if (k.includes('mp4') || k.includes('mov') || k.includes('webm') || k === 'video') return 'video'; if (k.includes('mp3') || k.includes('wav') || k.includes('m4a') || k === 'audio') return 'audio'; if (k.includes('jpg') || k.includes('jpeg') || k.includes('png') || k.includes('webp') || k === 'image') return 'image'; if (k.includes('pptx') || k === 'ppt') return 'ppt'; if (k.includes('pdf')) return 'pdf'; if (k === 'doc' || k.includes('docx')) return 'doc'; if (k.startsWith('http')) return 'url'; return 'doc'; }; // Build PALETTE_DATA for one project. Pulls files from project.folders, merges // in analyses from /api/state. The "blocks" array is page/scene-level. const v2BuildPaletteData = (state, projectId) => { const project = (state.projects||[]).find(p => p.id === projectId); if (!project) return null; const analyses = state.analyses || {}; const files = []; for (const folder of project.folders || []) { for (const f of folder.files || []) { const ana = analyses[f.id]; const kind = v2NormalizeKind(f.kind || f.name || ''); files.push({ id: f.id, kind, name: f.name, size: f.size, pages: f.blocks ?? f.pages, duration: f.duration, status: f.status === 'analyzed' ? 'analyzed' : f.status === 'analyzing' ? 'analyzing' : f.status === 'idle' ? 'not_analyzed' : f.status || 'pending', progress: f.progress || (f.status === 'analyzed' ? 1 : 0), folder: folder.name, updated: f.date || '', ref_origin: f.ref_origin, ref_url: f.ref_url, analysis: ana || null, }); } } // Build blocks from analyses[file.id].blocks const blocks = []; for (const f of files) { const ana = analyses[f.id]; if (!ana) continue; const blockList = ana.blocks || []; if (!blockList.length && ana.summary) { // Single-block fallback (legacy flat analyses) blocks.push({ id: `${f.id}_b0`, file: f.id, index: 1, kind: 'page', title: ana.summary.split(/[โ€”โ€”.ยท]/)[0].slice(0,60) || f.name, excerpt: ana.summary, summary: ana.summary, summaryEditedBy: 'ai', keywords: (ana.keywords || []).map((k,i) => v2KeywordToMockup(k, `${f.id}_b0_k${i}`)), }); continue; } blockList.forEach((b, i) => { const blockId = b.id || `${f.id}_b${i}`; const blockKeywords = (b.keywords || []).map((k,j) => v2KeywordToMockup(k, `${blockId}_k${j}`, blockId, f.id)); blocks.push({ id: blockId, file: f.id, index: i + 1, kind: f.kind === 'video' ? 'scene' : 'page', timecode: b.timecode || (f.kind === 'video' && b.scenes && b.scenes[0]?.timecode), title: b.title || `${f.kind === 'video' ? 'Scene' : 'p.'}${i+1}`, excerpt: (b.summary || '').slice(0, 240), summary: b.summary || '', summaryEditedBy: 'ai', keywords: blockKeywords, }); }); } // pinned/watching keep their backend ids; if missing fall back to first 6 metaphor-tier keywords const board = (state.boards || {})[projectId] || {pinned: [], watching: []}; const pinned = (board.pinned || []).map(p => p._id || p.id || `${p.w}|${p.ko}`); const watching = (board.watching || []).map(p => p._id || p.id || `${p.w}|${p.ko}`); return { project: { id: project.id, name: project.name, code: project.client || '', status: 'live', brief: project.brief || '' }, files, blocks, pinned, watching, distilled: (state.distilled || {})[projectId] || [], references: (state.references || {})[projectId] || {saved: [], last_results: []}, }; }; const V2_TIER_FROM_KW = (k) => { if (k.tier) return (V2_TIER_MAP[k.tier] || k.tier).toUpperCase(); if (k.type) return String(k.type).toUpperCase(); return 'LITERAL'; }; const v2KeywordToMockup = (k, id, blockId, fileId) => ({ id, type: V2_TIER_FROM_KW(k), en: k.w || k.en || '', ko: k.ko || '', gloss: k.why || k.gloss || '', heat: k.heat, block_id: k.block_id || blockId, blockId: k.block_id || blockId, // mockup-style camelCase used by mini-chat fileId: fileId, // critical: backend mutate needs this raw: k, // keep original for backend calls }); window.v2BuildPaletteData = v2BuildPaletteData; window.v2NormalizeKind = v2NormalizeKind;