UNPKG

@kaifronsdal/transcript-viewer

Version:

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

204 lines (201 loc) 8.08 kB
import { json, error } from '@sveltejs/kit'; import { promises } from 'fs'; import path from 'path'; import { T as TRANSCRIPT_DIR, g as getTranscriptCache } from './transcript-cache-CC28Y9w0.js'; import { D as DEFAULT_CONCURRENCY } from './constants-C6XQO3qi.js'; import { e as extractModelName } from './transcript-utils-BY7i01oF.js'; import 'chokidar'; import 'events'; import 'ajv'; import 'ajv-formats'; if (typeof window !== "undefined") { throw new Error("file-scanner can only be used on the server side"); } async function scanDirectoryForTranscripts(dirPath, basePath = dirPath) { const files = []; const directories = []; const errors = []; try { const entries = await promises.readdir(dirPath, { withFileTypes: true }); const transcriptFiles = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => { const fullPath = path.join(dirPath, entry.name); return path.relative(basePath, fullPath); }); const subdirs = entries.filter((entry) => entry.isDirectory()); files.push(...transcriptFiles); 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); const 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 (error2) { const loadingError = { type: "permission_denied", message: `Failed to scan directory: ${subdirPath}`, file: subdirPath, details: error2 instanceof Error ? error2.message : String(error2) }; errors.push(loadingError); } } } catch (error2) { const loadingError = { type: "permission_denied", message: `Failed to scan directory: ${dirPath}`, file: dirPath, details: error2 instanceof Error ? error2.message : String(error2) }; errors.push(loadingError); } return { files, directories, errors }; } async function checkPathAccess(dirPath) { try { await promises.access(dirPath); return true; } catch { return false; } } if (typeof window !== "undefined") { throw new Error("cached-bulk-loader can only be used on the server side"); } async function loadCachedTranscriptsMetadataOnly(outputDir = "./transcripts", includeErrors = true) { console.log(`📂 [CACHED-LOADER] Starting cached metadata loading from: ${outputDir}`); const directoryExists = await checkPathAccess(outputDir); if (!directoryExists) { const error2 = { type: "file_not_found", message: `Directory not found: ${outputDir}`, file: outputDir }; return { transcripts: [], errors: [error2], directories: [], stats: { totalFiles: 0, successfulFiles: 0, failedFiles: 1, validationErrors: 0, parseErrors: 0, totalDirectories: 0, emptyDirectories: 0 } }; } console.log("🔍 [CACHED-LOADER] Scanning directory structure..."); const scanResult = await scanDirectoryForTranscripts(outputDir); console.log(`📁 [CACHED-LOADER] Found ${scanResult.files.length} files and ${scanResult.directories.length} directories`); const cache = getTranscriptCache(); console.log("⚙️ [CACHED-LOADER] Loading metadata through cache..."); const transcripts = []; const errors = [...scanResult.errors]; const concurrency = DEFAULT_CONCURRENCY; const chunks = chunkArray(scanResult.files, concurrency); for (const chunk of chunks) { const results = await Promise.allSettled( chunk.map(async (relativePath) => { try { const absolutePath = path.resolve(outputDir, relativePath); const metadata = await cache.getMetadata(absolutePath); if (metadata) { const pathParts = relativePath.split("/"); const behaviorDir = pathParts.length > 1 ? pathParts[pathParts.length - 2] : ""; const fileName = pathParts[pathParts.length - 1]; const transcriptNumber = fileName.endsWith(".json") ? fileName.slice(0, -5) : fileName; const summary = metadata.judge_output?.summary || metadata.description || "No summary available"; const transcript = { id: metadata.transcript_id || transcriptNumber, model: extractModelName(metadata.target_model || ""), split: behaviorDir, concerningScore: metadata.judge_output?.scores?.concerning || 0, summary: summary.length > 200 ? summary.substring(0, 200) + "..." : summary, // Truncate long summaries scores: metadata.judge_output?.scores || {}, scoreDescriptions: metadata.judge_output?.score_descriptions, judgeSummary: "", // Remove heavy field - not needed for homepage justification: "", // Remove heavy field - not needed for homepage tags: metadata.tags || [], systemPrompt: void 0, // Cannot extract system prompt from metadata-only (no events) transcript: void 0, // Remove heavy field - not needed for homepage _filePath: relativePath // Store relative path }; return transcript; } return null; } catch (error2) { throw { filePath: relativePath, error: error2 }; } }) ); for (const result of results) { if (result.status === "fulfilled" && result.value) { transcripts.push(result.value); } else if (result.status === "rejected") { const rejection = result.reason; errors.push({ type: "unknown_error", message: `Failed to load metadata: ${rejection.filePath}`, file: rejection.filePath, details: rejection.error instanceof Error ? rejection.error.message : String(rejection.error) }); } } } console.log(`✅ [CACHED-LOADER] Successfully loaded ${transcripts.length} transcript metadata`); console.log(`❌ [CACHED-LOADER] Failed to load ${errors.length - scanResult.errors.length} files`); const stats = { totalFiles: scanResult.files.length, successfulFiles: transcripts.length, failedFiles: errors.length - scanResult.errors.length, validationErrors: errors.filter((e) => e.type === "validation_error").length, parseErrors: errors.filter((e) => e.type === "parse_error").length, totalDirectories: scanResult.directories.length, emptyDirectories: scanResult.directories.filter((d) => d.isEmpty).length }; console.log("📈 [CACHED-LOADER] Final statistics:", stats); return { transcripts, errors: includeErrors ? errors : [], directories: scanResult.directories, stats }; } function chunkArray(array, chunkSize) { const chunks = []; for (let i = 0; i < array.length; i += chunkSize) { chunks.push(array.slice(i, i + chunkSize)); } return chunks; } const GET = async ({ url }) => { try { const subdirectoryPath = url.searchParams.get("rootDir"); const rootDir = subdirectoryPath ? path.resolve(TRANSCRIPT_DIR, subdirectoryPath) : TRANSCRIPT_DIR; const result = await loadCachedTranscriptsMetadataOnly(rootDir); return json(result.transcripts); } catch (err) { console.error("Metadata list API error:", err); throw error(500, { message: "Failed to load transcript metadata list" }); } }; export { GET }; //# sourceMappingURL=_server.ts-vLxRuuRU.js.map