UNPKG

@kaifronsdal/transcript-viewer

Version:

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

126 lines (110 loc) 3.74 kB
import { promises as fs } from 'fs'; import path from 'path'; import type { DirectoryInfo, LoadingError } from './types'; // Server-side check if (typeof window !== 'undefined') { throw new Error('file-scanner can only be used on the server side'); } /** * Scan a directory for transcript files (.json files) */ export async function scanDirectoryForTranscripts( dirPath: string, basePath: string = dirPath ): Promise<{ files: string[]; directories: DirectoryInfo[]; errors: LoadingError[] }> { const files: string[] = []; const directories: DirectoryInfo[] = []; const errors: LoadingError[] = []; try { const entries = await fs.readdir(dirPath, { withFileTypes: true }); // Separate files and directories const transcriptFiles = entries .filter(entry => entry.isFile() && entry.name.endsWith('.json')) .map(entry => { const fullPath = path.join(dirPath, entry.name); // Return path relative to basePath return path.relative(basePath, fullPath); }); const subdirs = entries.filter(entry => entry.isDirectory()); files.push(...transcriptFiles); // Process subdirectories for (const subdir of subdirs) { const subdirPath = path.join(dirPath, subdir.name); const relativePath = path.relative(basePath, subdirPath); try { const subdirResult = await scanDirectoryForTranscripts(subdirPath, basePath); files.push(...subdirResult.files); directories.push(...subdirResult.directories); errors.push(...subdirResult.errors); // Create directory info const directoryInfo: DirectoryInfo = { path: subdirPath, relativePath, isEmpty: subdirResult.files.length === 0 && subdirResult.directories.length === 0, hasTranscripts: subdirResult.files.length > 0, transcriptCount: subdirResult.files.length, subdirectoryCount: subdirResult.directories.length }; directories.push(directoryInfo); } catch (error) { const loadingError: LoadingError = { type: 'permission_denied', message: `Failed to scan directory: ${subdirPath}`, file: subdirPath, details: error instanceof Error ? error.message : String(error) }; errors.push(loadingError); } } } catch (error) { const loadingError: LoadingError = { type: 'permission_denied', message: `Failed to scan directory: ${dirPath}`, file: dirPath, details: error instanceof Error ? error.message : String(error) }; errors.push(loadingError); } return { files, directories, errors }; } /** * Check if a path exists and is accessible */ export async function checkPathAccess(dirPath: string): Promise<boolean> { try { await fs.access(dirPath); return true; } catch { return false; } } /** * Get directory stats without recursing into subdirectories */ export async function getDirectoryStats(dirPath: string): Promise<{ exists: boolean; fileCount: number; subdirCount: number; transcriptCount: number; }> { try { const entries = await fs.readdir(dirPath, { withFileTypes: true }); const fileCount = entries.filter(entry => entry.isFile()).length; const subdirCount = entries.filter(entry => entry.isDirectory()).length; const transcriptCount = entries .filter(entry => entry.isFile() && entry.name.endsWith('.json')).length; return { exists: true, fileCount, subdirCount, transcriptCount }; } catch { return { exists: false, fileCount: 0, subdirCount: 0, transcriptCount: 0 }; } }