Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 100 additions & 130 deletions src/components/bulk/DetailDrawer.astro
Original file line number Diff line number Diff line change
Expand Up @@ -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
* <DetailDrawer result={result} rowData={rowData} />
* <DetailDrawer allRowResults={allRowResults} rowData={rowData} rowIndex={rowIndex} />
*
* @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<string, string>;
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;
---

<Drawer id="bulk-detail-drawer" width="lg">
<Drawer id="bulk-detail-drawer" width="xl">
<Fragment slot="title">
<h2
class="font-display text-2xl font-semibold text-gradient-gold"
Expand All @@ -40,8 +43,8 @@ void rowData;

<!-- Content -->
<div class="space-y-6">
<!-- Status Indicator -->
<div id="bulk-detail-status" class="flex items-center gap-2">
<!-- Row Index Header -->
<div id="bulk-detail-row-index" class="flex items-center gap-2">
<!-- Populated by client script -->
</div>

Expand All @@ -57,60 +60,9 @@ void rowData;
</div>
</div>

<!-- Interpolated Prompt -->
<div>
<h3 class="text-sm font-semibold text-base-content/70 mb-2">Interpolated Prompt:</h3>
<div
id="bulk-detail-prompt"
class="bg-base-200 p-3 rounded-lg text-sm whitespace-pre-wrap font-mono max-h-48 overflow-y-auto"
>
<!-- Populated by client script -->
</div>
</div>

<!-- Model Output -->
<div>
<h3 class="text-sm font-semibold text-base-content/70 mb-2">Model Output:</h3>
<div
id="bulk-detail-output"
class="bg-base-200 p-3 rounded-lg text-sm whitespace-pre-wrap max-h-64 overflow-y-auto"
>
<!-- Populated by client script -->
</div>
</div>

<!-- Error Message (shown only when failed) -->
<div id="bulk-detail-error-section" class="hidden">
<h3 class="text-sm font-semibold text-base-content/70 mb-2">Error:</h3>
<div
id="bulk-detail-error"
class="bg-error/10 border border-error/30 p-3 rounded-lg text-sm text-error"
>
<!-- Populated by client script -->
</div>
</div>

<!-- Metadata -->
<div>
<h3 class="text-sm font-semibold text-base-content/70 mb-2">Execution Metadata:</h3>
<div class="grid grid-cols-2 gap-3">
<div class="bg-base-200 p-3 rounded-lg">
<div class="text-xs text-base-content/50">Model</div>
<div class="font-semibold text-sm truncate" id="bulk-detail-model">-</div>
</div>
<div class="bg-base-200 p-3 rounded-lg">
<div class="text-xs text-base-content/50">Row Index</div>
<div class="font-semibold tabular-nums" id="bulk-detail-row-index">-</div>
</div>
<div class="bg-base-200 p-3 rounded-lg">
<div class="text-xs text-base-content/50">Execution Time</div>
<div class="font-semibold tabular-nums" id="bulk-detail-duration">-</div>
</div>
<div class="bg-base-200 p-3 rounded-lg">
<div class="text-xs text-base-content/50">Created At</div>
<div class="font-semibold text-sm" id="bulk-detail-created">-</div>
</div>
</div>
<!-- Model Outputs - Vertical Stack -->
<div id="bulk-detail-model-outputs" class="space-y-6">
<!-- Populated by client script - one section per model -->
</div>
</div>
</Drawer>
Expand All @@ -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<string, string>) {
function showDetails(
allRowResults: RowResult[],
rowData: Record<string, string>,
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 = `
<span class="badge badge-${result.status === 'completed' ? 'success' : result.status === 'failed' ? 'error' : 'warning'}">
${statusLabels[result.status] || result.status}
</span>
// Update row index header
const rowIndexHeader = document.getElementById('bulk-detail-row-index');
if (rowIndexHeader) {
rowIndexHeader.innerHTML = `
<span class="badge badge-neutral badge-lg">Row #${rowIndex + 1}</span>
`;
}

Expand All @@ -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 =
'<span class="italic text-base-content/40">No output (failed)</span>';
outputEl.classList.remove('font-mono');
} else {
outputEl.innerHTML = '<span class="italic text-base-content/40">Pending...</span>';
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 `
<div class="border border-base-300 rounded-lg overflow-hidden">
<!-- Model Header -->
<div class="bg-base-200 px-4 py-3 flex items-center justify-between">
<div class="flex items-center gap-3">
<h4 class="font-semibold text-base-content">${result.model_id}</h4>
<span class="badge ${statusBadge} badge-sm">
${statusLabels[result.status] || result.status}
</span>
</div>
<div class="flex items-center gap-4 text-sm text-base-content/60">
${result.duration_ms ? `<span class="tabular-nums">${result.duration_ms}ms</span>` : ''}
</div>
</div>

<!-- Model Content -->
<div class="p-4 space-y-4">
<!-- Interpolated Prompt -->
<div>
<h5 class="text-xs font-semibold text-base-content/60 mb-2 uppercase tracking-wide">Prompt:</h5>
<div class="bg-base-200 p-3 rounded-lg text-sm whitespace-pre-wrap font-mono max-h-32 overflow-y-auto">
${result.prompt_used || '<span class="italic text-base-content/40">No prompt</span>'}
</div>
</div>

<!-- Model Output -->
<div>
<h5 class="text-xs font-semibold text-base-content/60 mb-2 uppercase tracking-wide">Output:</h5>
<div class="bg-base-200 p-3 rounded-lg text-sm whitespace-pre-wrap max-h-48 overflow-y-auto">
${
result.status === 'completed' && result.output_text
? `<pre class="font-mono text-sm">${result.output_text}</pre>`
: result.status === 'failed'
? '<span class="italic text-base-content/40">No output (failed)</span>'
: '<span class="italic text-base-content/40">Pending...</span>'
}
</div>
</div>

<!-- Error Message (shown only when failed) -->
${
result.status === 'failed' && result.error_message
? `
<div>
<h5 class="text-xs font-semibold text-base-content/60 mb-2 uppercase tracking-wide">Error:</h5>
<div class="bg-error/10 border border-error/30 p-3 rounded-lg text-sm text-error">
${result.error_message}
</div>
</div>
`
: ''
}
</div>
</div>
`;
})
.join('');
}

// Open drawer
Expand Down
Loading
Loading