@kaifronsdal/transcript-viewer
Version:
A web-based viewer for AI conversation transcripts with rollback support
237 lines (201 loc) • 7.64 kB
text/typescript
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;
}