@kaifronsdal/transcript-viewer
Version:
A web-based viewer for AI conversation transcripts with rollback support
150 lines (128 loc) • 4.58 kB
text/typescript
import type { TranscriptDisplay, TableRow } from '$lib/shared/types';
/**
* Build a folder tree structure from transcripts only (client-side)
* This is more efficient than server-side building since we don't need directory scanning
* Uses _filePath to properly handle nested directory structures
*/
export function buildFolderTreeFromTranscripts(
transcripts: TranscriptDisplay[]
): TableRow[] {
const rootNode: TableRow = {
id: 'root',
name: 'root',
path: '',
type: 'folder' as const,
subRows: [],
isEmpty: false,
transcriptCount: 0
};
// Create a map for quick folder lookup
const folderMap = new Map<string, TableRow>();
folderMap.set('', rootNode); // Root folder
// Process each transcript and build the nested folder structure
for (const transcript of transcripts) {
// Extract directory path from _filePath (e.g., "folder1/subfolder2/transcript_123.json" -> "folder1/subfolder2")
const filePath = transcript._filePath || '';
const pathParts = filePath.split('/');
// Remove the filename (last part) to get directory path
const directoryParts = pathParts.slice(0, -1);
const directoryPath = directoryParts.join('/');
// console.log('🏗️ [CLIENT-TREE] Processing transcript:', {
// id: transcript.id,
// filePath,
// directoryParts,
// directoryPath
// });
// Create nested folder structure
let currentPath = '';
let currentFolder = rootNode;
for (const folderName of directoryParts) {
const parentPath = currentPath;
currentPath = currentPath ? `${currentPath}/${folderName}` : folderName;
// Create folder if it doesn't exist
if (!folderMap.has(currentPath)) {
const folder: TableRow = {
id: currentPath,
name: folderName,
path: currentPath,
type: 'folder' as const,
subRows: [],
isEmpty: false,
transcriptCount: 0
};
folderMap.set(currentPath, folder);
currentFolder.subRows!.push(folder);
}
currentFolder = folderMap.get(currentPath)!;
}
// Add transcript to the deepest folder
const transcriptNode: TableRow = {
id: directoryPath ? `${directoryPath}/${transcript.id}` : transcript.id,
name: transcript.summary.substring(0, 50) + (transcript.summary.length > 50 ? '...' : ''),
path: directoryPath ? `${directoryPath}/${transcript.id}` : 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,
concerningScore: transcript.concerningScore,
judgeSummary: transcript.judgeSummary,
justification: transcript.justification,
tags: transcript.tags,
originalTranscript: transcript
};
currentFolder.subRows!.push(transcriptNode);
}
// Update transcript counts for all folders
updateFolderCounts(rootNode);
// Sort folders and transcripts within each folder
sortFolderTree(rootNode);
return rootNode.subRows || [];
}
/**
* Recursively update transcript counts for all folders
*/
function updateFolderCounts(node: TableRow): number {
if (node.type === 'transcript') {
return 1;
}
if (node.type === 'folder' && node.subRows) {
let totalCount = 0;
for (const child of node.subRows) {
totalCount += updateFolderCounts(child);
}
node.transcriptCount = totalCount;
node.isEmpty = totalCount === 0;
return totalCount;
}
return 0;
}
/**
* 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);
}
}
}