// v2 ANALYSIS stage — sources pane + 5-column kanban blocks + pinned board + mini-chat const V2_TYPES = ['LITERAL','SENSORY','EMOTION','UNIVERSAL','METAPHOR']; const V2SourcesPane = ({palette, selectedFile, setSelectedFile, onAnalyzeFile})=>{ const [mode, setMode] = React.useState('tags'); // 'tags' | 'timeline' | 'visual' const [search, setSearch] = React.useState(''); const [activeTags, setActiveTags] = React.useState(new Set(['all'])); const files = palette.files || []; const visible = files.filter(f => !search || f.name.toLowerCase().includes(search.toLowerCase())); return ( ); }; const V2SourcesTagsView = ({files, selectedFile, setSelectedFile, activeTags, setActiveTags}) => { // Auto-derive tags from folder names + kinds const tagCounts = { all: files.length }; files.forEach(f => { const k = f.kind || 'doc'; tagCounts[k] = (tagCounts[k]||0) + 1; const folder = (f.folder||'').replace(/^\d+\s*[·.]?\s*/,'').toLowerCase(); if (folder) tagCounts[folder] = (tagCounts[folder]||0) + 1; }); const allTags = Object.keys(tagCounts); const toggleTag = (tag) => setActiveTags(prev => { const next = new Set(prev); if (tag === 'all') return new Set(['all']); next.delete('all'); if (next.has(tag)) next.delete(tag); else next.add(tag); if (next.size === 0) return new Set(['all']); return next; }); const filtered = activeTags.has('all') ? files : files.filter(f => { const folder = (f.folder||'').replace(/^\d+\s*[·.]?\s*/,'').toLowerCase(); return activeTags.has(f.kind) || activeTags.has(folder); }); return ( <>
{allTags.map(tag => ( ))}
{filtered.map(f => setSelectedFile(f.id)}/>)} {!filtered.length &&
이 필터에 맞는 자료가 없어요.
}
); }; const V2SourcesTimelineView = ({files, selectedFile, setSelectedFile}) => { // Sort by date asc; show with timeline rail. const ordered = [...files].sort((a,b) => (a.date||'').localeCompare(b.date||'')); return (
{ordered.map((f) => { const meta = v2FileMeta(f.kind); const active = f.id === selectedFile; const dot = f.status === 'analyzing' ? 'live' : f.status === 'analyzed' ? 'done' : ''; return (
); })} {!ordered.length &&
업로드된 자료 없음.
}
); }; const V2SourcesVisualView = ({files, selectedFile, setSelectedFile}) => { return (
{files.map(f => { const meta = v2FileMeta(f.kind); const active = f.id === selectedFile; const isMedia = f.kind === 'image' || f.kind === 'video'; const url = isMedia ? window.API.fileRawUrl(f.id) : null; return ( ); })} {!files.length &&
업로드된 자료 없음.
}
); }; const V2SourceFileRow = ({f, active, onClick}) => { const meta = v2FileMeta(f.kind); return ( ); }; const V2AnalysisBlock = ({block, palette, projectId, onPin, onChipClick, onIdeate, onSaveSummary, pinnedSet})=>{ const file = (palette.files || []).find(f => f.id === block.file); const meta = file ? v2FileMeta(file.kind) : v2FileMeta('doc'); const [collapsed, setCollapsed] = React.useState(false); const [editing, setEditing] = React.useState(false); const [savingSummary, setSavingSummary] = React.useState(false); const [ideating, setIdeating] = React.useState(false); const [text, setText] = React.useState(block.summary); React.useEffect(() => setText(block.summary), [block.id, block.summary]); const editedByUser = block.summaryEditedBy === 'user'; const commit = async () => { setEditing(false); if ((text||'') === (block.summary||'')) return; setSavingSummary(true); try { await onSaveSummary?.(block, text); } finally { setSavingSummary(false); } }; return (

{block.title}

{file?.name}{block.timecode ? ` · ${block.timecode}` : ''} {savingSummary && ( 저장 중… )}
{(block.keywords || []).length} keywords
{editing ? ( <>