diff --git a/src/components/bulk/DetailDrawer.astro b/src/components/bulk/DetailDrawer.astro index b576a86..9fd4329 100644 --- a/src/components/bulk/DetailDrawer.astro +++ b/src/components/bulk/DetailDrawer.astro @@ -2,33 +2,36 @@ /** * DetailDrawer Component * - * Slide-out drawer for displaying full details of a bulk evaluation row result. - * Shows original CSV row data, interpolated prompt, model output, and execution metadata. + * Slide-out drawer for displaying full details of a bulk evaluation row. + * Shows ALL model outputs for a row in a vertical stack. * * @example - * + * * - * @param result - RowResult containing prompt, output, status, and metadata + * @param allRowResults - Array of RowResult for all models on this row * @param rowData - Original CSV row data as key-value pairs + * @param rowIndex - The original row index for display * - * Opens via `window.showBulkRowDetails(result, rowData)` global function. + * Opens via `window.showBulkRowDetails(allRowResults, rowData, rowIndex)` global function. */ import Drawer from '@components/ui/Drawer.astro'; import type { RowResult } from '@lib/db'; interface Props { - result: RowResult; + allRowResults: RowResult[]; rowData: Record; + rowIndex: number; } // Props are used by client-side script via window.showBulkRowDetails() -const { result, rowData } = Astro.props; -void result; // Mark as used for client-side hydration +const { allRowResults, rowData, rowIndex } = Astro.props; +void allRowResults; // Mark as used for client-side hydration void rowData; +void rowIndex; --- - +

- -
+ +
@@ -57,60 +60,9 @@ void rowData;
- -
-

Interpolated Prompt:

-
- -
-
- - -
-

Model Output:

-
- -
-
- - - - - -
-

Execution Metadata:

-
-
-
Model
-
-
-
-
-
Row Index
-
-
-
-
-
Execution Time
-
-
-
-
-
Created At
-
-
-
-
+ +
+
@@ -125,29 +77,27 @@ void rowData; */ function initBulkDetailDrawer() { /** - * Populates the drawer with row result data and opens it. - * @param result - RowResult with evaluation data + * Populates the drawer with all model results for a row and opens it. + * @param allRowResults - Array of RowResult for all models on this row * @param rowData - Original CSV row data as key-value pairs + * @param rowIndex - The original row index for display */ - function showDetails(result: RowResult, rowData: Record) { + function showDetails( + allRowResults: RowResult[], + rowData: Record, + rowIndex: number + ) { // Update title const title = document.getElementById('bulk-detail-drawer-title'); if (title) { - title.textContent = `Row ${result.original_row_index + 1} Details - ${result.model_id}`; + title.textContent = `Row ${rowIndex + 1} Details`; } - // Update status indicator - const statusDiv = document.getElementById('bulk-detail-status'); - if (statusDiv) { - const statusLabels = { - completed: 'Completed', - failed: 'Failed', - pending: 'Pending', - }; - statusDiv.innerHTML = ` - - ${statusLabels[result.status] || result.status} - + // Update row index header + const rowIndexHeader = document.getElementById('bulk-detail-row-index'); + if (rowIndexHeader) { + rowIndexHeader.innerHTML = ` + Row #${rowIndex + 1} `; } @@ -166,55 +116,75 @@ void rowData; .join(''); } - // Update prompt - const promptEl = document.getElementById('bulk-detail-prompt'); - if (promptEl) { - promptEl.textContent = result.prompt_used || '-'; - } - - // Update output - const outputEl = document.getElementById('bulk-detail-output'); - if (outputEl) { - if (result.status === 'completed' && result.output_text) { - outputEl.textContent = result.output_text; - outputEl.classList.add('font-mono'); - outputEl.classList.remove('italic', 'text-base-content/40'); - } else if (result.status === 'failed') { - outputEl.innerHTML = - 'No output (failed)'; - outputEl.classList.remove('font-mono'); - } else { - outputEl.innerHTML = 'Pending...'; - outputEl.classList.remove('font-mono'); - } - } - - // Update error section - const errorSection = document.getElementById('bulk-detail-error-section'); - const errorEl = document.getElementById('bulk-detail-error'); - if (errorSection && errorEl) { - if (result.status === 'failed' && result.error_message) { - errorEl.textContent = result.error_message; - errorSection.classList.remove('hidden'); - } else { - errorSection.classList.add('hidden'); - } - } - - // Update metadata - const modelEl = document.getElementById('bulk-detail-model'); - const rowIndexEl = document.getElementById('bulk-detail-row-index'); - const durationEl = document.getElementById('bulk-detail-duration'); - const createdEl = document.getElementById('bulk-detail-created'); - - if (modelEl) modelEl.textContent = result.model_id; - if (rowIndexEl) rowIndexEl.textContent = `#${result.original_row_index + 1}`; - if (durationEl) { - durationEl.textContent = result.duration_ms ? `${result.duration_ms}ms` : '-'; - } - if (createdEl) { - const date = new Date(result.created_at); - createdEl.textContent = date.toLocaleString(); + // Render all model outputs in vertical stack + const modelOutputsContainer = document.getElementById('bulk-detail-model-outputs'); + if (modelOutputsContainer) { + modelOutputsContainer.innerHTML = allRowResults + .map((result) => { + const statusLabels = { + completed: 'Completed', + failed: 'Failed', + pending: 'Pending', + }; + const statusBadge = `badge-${result.status === 'completed' ? 'success' : result.status === 'failed' ? 'error' : 'warning'}`; + + return ` +
+ +
+
+

${result.model_id}

+ + ${statusLabels[result.status] || result.status} + +
+
+ ${result.duration_ms ? `${result.duration_ms}ms` : ''} +
+
+ + +
+ +
+
Prompt:
+
+ ${result.prompt_used || 'No prompt'} +
+
+ + +
+
Output:
+
+ ${ + result.status === 'completed' && result.output_text + ? `
${result.output_text}
` + : result.status === 'failed' + ? 'No output (failed)' + : 'Pending...' + } +
+
+ + + ${ + result.status === 'failed' && result.error_message + ? ` +
+
Error:
+
+ ${result.error_message} +
+
+ ` + : '' + } +
+
+ `; + }) + .join(''); } // Open drawer diff --git a/src/pages/bulk-eval/[id].astro b/src/pages/bulk-eval/[id].astro index 8c42223..4e48f79 100644 --- a/src/pages/bulk-eval/[id].astro +++ b/src/pages/bulk-eval/[id].astro @@ -14,7 +14,6 @@ import Layout from '@layouts/Layout.astro'; import DetailDrawer from '@components/bulk/DetailDrawer.astro'; import Icon from '@components/ui/Icon.astro'; -import type { RowResult } from '@lib/db'; // Get run ID from URL params const { id } = Astro.params; @@ -277,7 +276,7 @@ if (notFound || !initialData) { - } /> + } rowIndex={0} />
@@ -296,14 +295,19 @@ if (notFound || !initialData) { // Declare the global function added by DetailDrawer declare global { interface Window { - showBulkRowDetails?: (result: RowResult, rowData: Record) => void; + showBulkRowDetails?: ( + allRowResults: RowResult[], + rowData: Record, + rowIndex: number + ) => void; } } // Define custom event detail type for bulk-row-view-details event interface BulkRowViewDetailsEventDetail { - rowResult: RowResult; + allRowResults: RowResult[]; rowData: Record; + rowIndex: number; } // Get run ID from data attribute @@ -319,9 +323,9 @@ if (notFound || !initialData) { */ document.addEventListener('bulk-row-view-details', (e: Event) => { const customEvent = e as CustomEvent; - const { rowResult, rowData } = customEvent.detail; + const { allRowResults, rowData, rowIndex } = customEvent.detail; if (window.showBulkRowDetails) { - window.showBulkRowDetails(rowResult, rowData); + window.showBulkRowDetails(allRowResults, rowData, rowIndex); } }); @@ -614,14 +618,14 @@ if (notFound || !initialData) { .map( (row) => ` - + ${row.index + 1} ${headers .map((header: string, cellIndex: number) => { const value = String(row.data[header] ?? ''); return ` - +
${value || '-'}
@@ -662,7 +666,7 @@ if (notFound || !initialData) { } return ` - + ${cellContent} `; @@ -687,7 +691,7 @@ if (notFound || !initialData) { } /** - * Handle row click to show details + * Handle row click to show details for all models */ async function handleRowClick(event: Event): Promise { const target = event.target as HTMLElement; @@ -697,40 +701,40 @@ if (notFound || !initialData) { const rowIndex = parseInt(row.getAttribute('data-row-index') || '-1'); if (rowIndex < 0) return; - // Get the model_id from the clicked cell - const cell = target.closest('td[data-model-id]'); - const modelId = cell?.getAttribute('data-model-id'); - - if (!modelId) { - addToast('Click on a model output cell to view details', 'info'); - return; - } - - // Find the result for this row and model + // Get the row data const rowData = currentData.rows[rowIndex]; if (!rowData) return; - const result = rowData.results[modelId]; - if (!result) return; - - // Convert result to RowResult format expected by DetailDrawer - const rowResult: RowResult = { - id: `${currentData.run.id}-${rowIndex}-${modelId}`, // synthetic ID - run_id: currentData.run.id, - original_row_index: rowIndex, - model_id: modelId, - prompt_used: currentData.run.system_prompt || '', - output_text: result.output_text, - status: result.status as 'pending' | 'completed' | 'failed', - error_message: result.error_message, - duration_ms: result.duration_ms, - created_at: currentData.run.created_at || '', - }; + // Get run data with null check + const runId = currentData.run.id; + const systemPrompt = currentData.run.system_prompt || ''; + const createdAt = currentData.run.created_at || ''; + + // Convert all model results to RowResult format + const allRowResults: RowResult[] = currentData.selected_models.map((model) => { + const result = rowData.results[model.id]; + return { + id: `${runId}-${rowIndex}-${model.id}`, // synthetic ID + run_id: runId, + original_row_index: rowIndex, + model_id: model.id, + prompt_used: systemPrompt, + output_text: result?.output_text, + status: (result?.status as 'pending' | 'completed' | 'failed') || 'pending', + error_message: result?.error_message, + duration_ms: result?.duration_ms, + created_at: createdAt, + }; + }); // Dispatch custom event for the detail drawer to handle const detailEvent = new CustomEvent('bulk-row-view-details', { bubbles: true, - detail: { rowResult, rowData: rowData.data as Record }, + detail: { + allRowResults, + rowData: rowData.data as Record, + rowIndex, + }, }); document.dispatchEvent(detailEvent); } diff --git a/src/pages/bulk-eval/new.astro b/src/pages/bulk-eval/new.astro index a3cafd9..12e8639 100644 --- a/src/pages/bulk-eval/new.astro +++ b/src/pages/bulk-eval/new.astro @@ -17,7 +17,6 @@ import ConfigPanel from '@components/bulk/ConfigPanel.astro'; import DetailDrawer from '@components/bulk/DetailDrawer.astro'; import { getModels } from '@lib/db'; import type { ModelConfiguration } from '@lib/utils/types'; -import type { RowResult } from '@lib/db'; // Fetch available models for configuration panel let models: ModelConfiguration[] = []; @@ -143,7 +142,7 @@ try { - } /> + } rowIndex={0} />
@@ -173,7 +172,11 @@ try { // Declare the global function added by DetailDrawer declare global { interface Window { - showBulkRowDetails?: (result: RowResult, rowData: Record) => void; + showBulkRowDetails?: ( + allRowResults: RowResult[], + rowData: Record, + rowIndex: number + ) => void; } } @@ -736,7 +739,7 @@ try { }; /** - * Handle row click to show details + * Handle row click to show details for all models */ const handleRowClick = (event: Event) => { const target = event.target as HTMLElement; @@ -746,44 +749,39 @@ try { const rowIndex = parseInt(row.getAttribute('data-row-index') || '-1'); if (rowIndex < 0) return; - // Get the model_id from the clicked cell - const cell = target.closest('td[data-model-id]'); - const modelId = cell?.getAttribute('data-model-id'); - - if (!modelId) { - addToast('Click on a model output cell to view details', 'info'); - return; - } - - // Find the result for this row and model + // Fetch all row data to get all model results fetch(`/api/bulk/results?run_id=${state.runId}`) .then((res) => res.json()) .then((data) => { - // API returns { rows: [{ index, data, results }], run: {...}, ... } + // API returns { rows: [{ index, data, results }], run: {...}, selected_models: [...], ... } const row = data.rows?.[rowIndex]; if (!row) return; - const result = row.results[modelId]; - if (!result) return; - - // Convert result to RowResult format expected by DetailDrawer - // Use run data for fields not included in the response - const rowResult: RowResult = { - id: `${data.run.id}-${rowIndex}-${modelId}`, // synthetic ID - run_id: data.run.id, - original_row_index: rowIndex, - model_id: modelId, - prompt_used: data.run.system_prompt || '', - output_text: result.output_text, - status: result.status, - error_message: result.error_message, - duration_ms: result.duration_ms, - created_at: data.run.created_at || '', - }; + const selectedModels = data.selected_models || []; + const runId = data.run.id; + const systemPrompt = data.run.system_prompt || ''; + const createdAt = data.run.created_at || ''; + + // Convert all model results to RowResult format + const allRowResults: RowResult[] = selectedModels.map((modelId: string) => { + const result = row.results[modelId]; + return { + id: `${runId}-${rowIndex}-${modelId}`, // synthetic ID + run_id: runId, + original_row_index: rowIndex, + model_id: modelId, + prompt_used: systemPrompt, + output_text: result?.output_text, + status: result?.status || 'pending', + error_message: result?.error_message, + duration_ms: result?.duration_ms, + created_at: createdAt, + }; + }); // Call the global function exposed by DetailDrawer if (window.showBulkRowDetails) { - window.showBulkRowDetails(rowResult, row.data as Record); + window.showBulkRowDetails(allRowResults, row.data as Record, rowIndex); } }) .catch(() => {