UNPKG

@kaifronsdal/transcript-viewer

Version:

A web-based viewer for AI conversation transcripts with rollback support

237 lines (201 loc) 7.64 kB
import type { TranscriptDisplay, TableRow } from '$lib/shared/types'; import type { DirectoryInfo } from '$lib/shared/types'; /** * Build a folder tree structure from transcripts and directory information */ export function buildFolderTreeFromTranscriptsAndDirectories( transcripts: TranscriptDisplay[], directories: DirectoryInfo[], includeEmptyFolders: boolean = true ): TableRow[] { const rootNode: TableRow = { id: 'root', name: 'root', path: '', type: 'folder' as const, subRows: [], // TanStack Table format isEmpty: false, transcriptCount: 0 }; // Create a map for quick folder lookup const folderMap = new Map<string, TableRow>(); folderMap.set('', rootNode); // Root folder // First, create all directories (including empty ones) for (const dirInfo of directories) { if (dirInfo.relativePath === 'root') continue; // Skip root // Skip empty directories if not including them if (!includeEmptyFolders && dirInfo.isEmpty) continue; const pathParts = dirInfo.relativePath.split('/').filter(part => part !== ''); let currentPath = ''; let currentNode = rootNode; let existingFolder = null; // Declare outside the loop // Navigate/create the folder structure for (const part of pathParts) { const parentPath = currentPath; currentPath = currentPath ? `${currentPath}/${part}` : part; // Check if this folder already exists existingFolder = folderMap.get(currentPath); if (!existingFolder) { // Create new folder in TanStack Table format existingFolder = { id: currentPath, name: part, path: currentPath, type: 'folder' as const, subRows: [], // TanStack Table format isEmpty: false, transcriptCount: 0 }; console.log('🏗️ [DEBUG] Created folder in tree builder:', { name: part, path: currentPath }); currentNode.subRows!.push(existingFolder); folderMap.set(currentPath, existingFolder); } currentNode = existingFolder; } // Set folder statistics if (existingFolder) { existingFolder.isEmpty = dirInfo.isEmpty; existingFolder.transcriptCount = dirInfo.transcriptCount; } } // Now add transcripts to their appropriate folders for (const transcript of transcripts) { const split = transcript.split; // This is the folder/split name console.log('📝 [DEBUG] Processing transcript for folder tree:', { transcriptId: transcript.id, split }); // Handle root directory transcripts (split is empty string) if (!split) { console.log('📝 [DEBUG] Adding root directory transcript:', transcript.id); // Add transcript directly to root node with all necessary fields const transcriptNode: TableRow = { id: transcript.id, name: transcript.summary.substring(0, 50) + (transcript.summary.length > 50 ? '...' : ''), path: transcript.id, type: 'transcript' as const, // Include all transcript data directly in the unified format model: transcript.model, split: transcript.split, summary: transcript.summary, scores: transcript.scores, scoreDescriptions: transcript.scoreDescriptions, concerningScore: transcript.concerningScore, judgeSummary: transcript.judgeSummary, justification: transcript.justification, tags: transcript.tags, originalTranscript: transcript }; rootNode.subRows!.push(transcriptNode); continue; } // Find the folder for this transcript by matching the folder name // Since split is just the folder name, we need to find the folder with that name let targetFolder = null; for (const [path, folder] of folderMap.entries()) { if (folder.name === split) { targetFolder = folder; console.log('📂 [DEBUG] Found folder by name match:', split, 'at path:', path); break; } } if (!targetFolder) { // Also try direct path lookup as fallback targetFolder = folderMap.get(split); } console.log('📂 [DEBUG] Looking for folder with split:', split, 'found:', !!targetFolder); if (!targetFolder) { // Create the folder if it doesn't exist (shouldn't happen if directories are complete) targetFolder = { id: split, name: split, path: split, type: 'folder' as const, subRows: [], // TanStack Table format isEmpty: false, transcriptCount: 1 }; rootNode.subRows!.push(targetFolder); folderMap.set(split, targetFolder); } // Add transcript as a child node in TanStack Table format const transcriptNode: TableRow = { id: `${split}/${transcript.id}`, name: transcript.summary.substring(0, 50) + (transcript.summary.length > 50 ? '...' : ''), path: `${split}/${transcript.id}`, type: 'transcript' as const, // Include all transcript data directly in the unified format model: transcript.model, split: transcript.split, summary: transcript.summary, scores: transcript.scores, scoreDescriptions: transcript.scoreDescriptions, concerningScore: transcript.concerningScore, judgeSummary: transcript.judgeSummary, justification: transcript.justification, tags: transcript.tags, originalTranscript: transcript }; targetFolder.subRows!.push(transcriptNode); // Update transcript count for the folder if (targetFolder.transcriptCount !== undefined) { targetFolder.transcriptCount++; } } // Sort folders and transcripts within each folder sortFolderTree(rootNode); return rootNode.subRows || []; } /** * Recursively sort folder tree: folders first (alphabetically), then transcripts (by ID) */ function sortFolderTree(node: TableRow) { if (node.type !== 'folder' || !node.subRows) return; // Sort subRows: folders first, then transcripts node.subRows.sort((a, b) => { // Folders come before transcripts if (a.type !== b.type) { return a.type === 'folder' ? -1 : 1; } // Within same type, sort alphabetically if (a.type === 'folder') { return a.name.localeCompare(b.name); } else { // For transcripts, sort by original transcript ID const aId = a.originalTranscript?.id || a.id; const bId = b.originalTranscript?.id || b.id; return aId.localeCompare(bId); } }); // Recursively sort children for (const child of node.subRows) { if (child.type === 'folder') { sortFolderTree(child); } } } /** * Count transcripts in a folder tree recursively */ export function countTranscriptsInTree(nodes: TableRow[]): number { let count = 0; for (const node of nodes) { if (node.type === 'transcript') { count++; } else if (node.type === 'folder' && node.subRows) { count += countTranscriptsInTree(node.subRows); } } return count; } /** * Flatten folder tree to get all transcripts */ export function extractTranscriptsFromTree(nodes: TableRow[]): TranscriptDisplay[] { const transcripts: TranscriptDisplay[] = []; for (const node of nodes) { if (node.type === 'transcript' && node.originalTranscript) { transcripts.push(node.originalTranscript); } else if (node.type === 'folder' && node.subRows) { transcripts.push(...extractTranscriptsFromTree(node.subRows)); } } return transcripts; }