diff --git a/app/api/evaluations/[id]/route.ts b/app/api/evaluations/[id]/route.ts index bdf4fc5e..98961ad8 100644 --- a/app/api/evaluations/[id]/route.ts +++ b/app/api/evaluations/[id]/route.ts @@ -53,11 +53,12 @@ export async function GET( const searchParams = request.nextUrl.searchParams; const exportFormat = searchParams.get('export_format') || 'row'; + const resyncScore = searchParams.get('resync_score') || 'false'; // Build URL with query parameters const url = new URL(`${backendUrl}/api/v1/evaluations/${id}`); url.searchParams.set('get_trace_info', 'true'); - url.searchParams.set('resync_score', 'false'); + url.searchParams.set('resync_score', resyncScore); url.searchParams.set('export_format', exportFormat); console.log(`[REAL BACKEND] Fetching evaluation ${id} from ${url.toString()}`); diff --git a/app/components/ConfigCard.tsx b/app/components/ConfigCard.tsx index 446665b4..096d1801 100644 --- a/app/components/ConfigCard.tsx +++ b/app/components/ConfigCard.tsx @@ -181,7 +181,7 @@ export default function ConfigCard({ }} > - Vector Store IDs ({allVectorStoreIds.length}) + Knowledge Base IDs ({allVectorStoreIds.length}) - Vector Store ID + Knowledge Base ID
-
+
-

Detailed Configuration

+

+ {configVersionInfo?.name || 'Detailed Configuration'} + {configVersionInfo?.version && v{configVersionInfo.version}} +

- - {/* Config Name and Version */} - {job.config_id && job.config_version && ( -
- {isLoadingConfig ? ( -
Loading config info...
- ) : configVersionInfo ? ( -
-
- - - - - {configVersionInfo.name} v{configVersionInfo.version} - -
-
- {configVersionInfo.provider && configVersionInfo.model && ( -
- Model: {configVersionInfo.provider}/{configVersionInfo.model} -
- )} - {configVersionInfo.type && ( -
- Type: {configVersionInfo.type.toUpperCase()} -
- )} - {configVersionInfo.temperature !== undefined && ( -
- Temperature: {configVersionInfo.temperature} -
- )} -
-
- ) : ( -
- Config ID: {job.config_id} (v{job.config_version}) -
- )} -
- )}
{/* Content */} @@ -220,6 +203,19 @@ export default function ConfigModal({ isOpen, onClose, job, assistantConfig }: C
+ {configVersionInfo?.knowledge_base_ids && configVersionInfo.knowledge_base_ids.length > 0 && ( +
+
Knowledge Base IDs
+
+ {configVersionInfo.knowledge_base_ids.join(', ')} +
+
+ )} + {(configVersionInfo?.temperature !== undefined || assistantConfig?.temperature !== undefined || job.config?.temperature !== undefined) && (
Temperature
@@ -258,7 +254,7 @@ export default function ConfigModal({ isOpen, onClose, job, assistantConfig }: C backgroundColor: '#fafafa', borderWidth: '1px', borderColor: '#e5e5e5', - color: '#171717' + color: '#000000' }} > {tool.type} @@ -266,20 +262,36 @@ export default function ConfigModal({ isOpen, onClose, job, assistantConfig }: C ))}
{configVersionInfo.tools.map((tool, idx) => ( - Array.isArray(tool.knowledge_base_ids) && tool.knowledge_base_ids.length > 0 && ( -
-
- Vector Store IDs ({tool.type}) +
+ {Array.isArray(tool.knowledge_base_ids) && tool.knowledge_base_ids.length > 0 && ( +
+
+ Knowledge Base IDs ({tool.type}) +
+
+ {tool.knowledge_base_ids.join(', ')} +
-
- {tool.knowledge_base_ids.join(', ')} + )} + {tool.max_num_results !== undefined && ( +
+
+ Max Results ({tool.type}) +
+
+ {tool.max_num_results} +
-
- ) + )} +
))}
)} @@ -296,7 +308,7 @@ export default function ConfigModal({ isOpen, onClose, job, assistantConfig }: C backgroundColor: '#fafafa', borderWidth: '1px', borderColor: '#e5e5e5', - color: '#171717' + color: '#000000' }} > {tool.type} @@ -304,27 +316,43 @@ export default function ConfigModal({ isOpen, onClose, job, assistantConfig }: C ))}
{job.config.tools.map((tool, idx) => ( - Array.isArray(tool.knowledge_base_ids) && tool.knowledge_base_ids.length > 0 && ( -
-
- Vector Store IDs ({tool.type}) +
+ {Array.isArray(tool.knowledge_base_ids) && tool.knowledge_base_ids.length > 0 && ( +
+
+ Knowledge Base IDs ({tool.type}) +
+
+ {tool.knowledge_base_ids.join(', ')} +
-
- {tool.knowledge_base_ids.join(', ')} + )} + {tool.max_num_results !== undefined && ( +
+
+ Max Results ({tool.type}) +
+
+ {tool.max_num_results} +
-
- ) + )} +
))}
)} {Array.isArray(assistantConfig?.knowledge_base_ids) && assistantConfig.knowledge_base_ids.length > 0 && (
-
Vector Store IDs
+
Knowledge Base IDs
); -} +} \ No newline at end of file diff --git a/app/components/ConfigSelector.tsx b/app/components/ConfigSelector.tsx index 84f92636..e0c78934 100644 --- a/app/components/ConfigSelector.tsx +++ b/app/components/ConfigSelector.tsx @@ -4,7 +4,7 @@ */ "use client" -import React, { useState, useEffect } from 'react'; +import { useState } from 'react'; import { useRouter } from 'next/navigation'; import { colors } from '@/app/lib/colors'; import { useConfigs, SavedConfig, formatRelativeTime } from '@/app/lib/useConfigs'; @@ -30,16 +30,31 @@ export default function ConfigSelector({ const router = useRouter(); const { configs, configGroups, isLoading, error } = useConfigs(); const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); // Find currently selected config const selectedConfig = configs.find( c => c.config_id === selectedConfigId && c.version === selectedVersion ); + // Filter config groups based on search query + const filteredConfigGroups = searchQuery.trim() + ? configGroups.filter(group => + group.name.toLowerCase().includes(searchQuery.toLowerCase()) + ) + : configGroups; + // Handle config selection const handleSelect = (config: SavedConfig) => { onConfigSelect(config.config_id, config.version); setIsDropdownOpen(false); + setSearchQuery(''); // Clear search on selection + }; + + // Handle dropdown close + const handleCloseDropdown = () => { + setIsDropdownOpen(false); + setSearchQuery(''); // Clear search on close }; // Build URL params preserving evaluation context @@ -204,48 +219,82 @@ export default function ConfigSelector({ <> {/* Dropdown Selector */}
-
+ ) : ( + /* Button when dropdown is closed */ + + {selectedConfig ? ( +
+
+ {selectedConfig.name} + + v{selectedConfig.version} + +
+
+ {selectedConfig.provider}/{selectedConfig.modelName} • T:{selectedConfig.temperature.toFixed(2)} +
+
+ ) : ( + Select a configuration... + )} + + + + + )} {/* Dropdown Menu */} {isDropdownOpen && ( @@ -256,7 +305,12 @@ export default function ConfigSelector({ border: `1px solid ${colors.border}`, }} > - {configGroups.map((group) => ( + {filteredConfigGroups.length === 0 ? ( +
+ {searchQuery ? `No configurations match "${searchQuery}"` : 'No configurations available'} +
+ ) : ( + filteredConfigGroups.map((group) => (
{/* Config group header */}
))}
- ))} + )))}
)}
@@ -381,7 +435,7 @@ export default function ConfigSelector({ {isDropdownOpen && (
setIsDropdownOpen(false)} + onClick={handleCloseDropdown} /> )}
diff --git a/app/components/DetailedResultsTable.tsx b/app/components/DetailedResultsTable.tsx index e63c66d1..8b7f7efe 100644 --- a/app/components/DetailedResultsTable.tsx +++ b/app/components/DetailedResultsTable.tsx @@ -5,7 +5,7 @@ * Supports both row format (individual traces) and grouped format (multiple answers per question) */ -import { IndividualScore, TraceScore, getScoreObject, normalizeToIndividualScores, isNewScoreObject, isNewScoreObjectV2, isGroupedFormat, GroupedTraceItem } from './types'; +import { IndividualScore, TraceScore, getScoreObject, normalizeToIndividualScores, hasSummaryScores, isNewScoreObjectV2, isGroupedFormat, GroupedTraceItem } from './types'; import { EvalJob } from './types'; interface DetailedResultsTableProps { @@ -15,7 +15,8 @@ interface DetailedResultsTableProps { export default function DetailedResultsTable({ job }: DetailedResultsTableProps) { const scoreObject = getScoreObject(job); - if (!scoreObject || (!isNewScoreObject(scoreObject) && !isNewScoreObjectV2(scoreObject))) { + // 1. First check: Does it have summary_scores at all? + if (!scoreObject || !hasSummaryScores(scoreObject)) { return (

@@ -25,19 +26,25 @@ export default function DetailedResultsTable({ job }: DetailedResultsTableProps) ); } - // Check if grouped format - if (isNewScoreObjectV2(scoreObject) && isGroupedFormat(scoreObject.traces)) { - return ; + // 2. Second check: Does it have traces? (NewScoreObjectV2) + if (isNewScoreObjectV2(scoreObject)) { + // Check if grouped format + if (isGroupedFormat(scoreObject.traces)) { + return ; + } + // Otherwise show row format } - // Normalize to IndividualScore format for backward compatibility + // 3. Try to normalize to IndividualScore format + // This handles NewScoreObjectV2 (with traces) const individual_scores = normalizeToIndividualScores(scoreObject); + // 4. If no individual scores available (e.g., BasicScoreObject with only summary_scores) if (!individual_scores || individual_scores.length === 0) { return (

- No individual scores available + No individual scores available. Only summary metrics are available for this evaluation.

); diff --git a/app/components/ScoreDisplay.tsx b/app/components/ScoreDisplay.tsx index cbefccfb..8299e0fa 100644 --- a/app/components/ScoreDisplay.tsx +++ b/app/components/ScoreDisplay.tsx @@ -5,7 +5,7 @@ "use client" import React from 'react'; -import { ScoreObject, isNewScoreObject, isNewScoreObjectV2 } from './types'; +import { ScoreObject, hasSummaryScores } from './types'; interface ScoreDisplayProps { score: ScoreObject | null; @@ -36,10 +36,8 @@ export default function ScoreDisplay({ score, errorMessage }: ScoreDisplayProps) ); } - // Handle new score format (V1 or V2) - show summary scores - const isNewFormat = isNewScoreObject(score) || isNewScoreObjectV2(score); - - if (isNewFormat && 'summary_scores' in score) { + // Handle score format with summary_scores (V1, V2, or Basic) + if (hasSummaryScores(score)) { const summaryScores = score.summary_scores || []; if (summaryScores.length === 0) { @@ -81,33 +79,32 @@ export default function ScoreDisplay({ score, errorMessage }: ScoreDisplayProps) ); } - // Display numeric scores in a compact format + // Display each numeric score in its own box return ( -
- {numericScores.map((summary) => ( -
- - {summary.name}: - - - {summary.avg !== undefined ? summary.avg.toFixed(2) : 'N/A'} - - {summary.std !== undefined && ( - - ±{summary.std.toFixed(2)} - - )} -
- ))} +
+ {numericScores.map((summary, idx) => { + const value = summary.avg !== undefined ? summary.avg.toFixed(2) : 'N/A'; + const std = summary.std !== undefined ? summary.std.toFixed(2) : null; + + return ( +
+ {summary.name}: + {value} + {std !== null && ( + ±{std} + )} +
+ ); + })}
); } @@ -127,4 +124,4 @@ export default function ScoreDisplay({ score, errorMessage }: ScoreDisplayProps) Unsupported format
); -} +} \ No newline at end of file diff --git a/app/components/prompt-editor/ConfigEditorPane.tsx b/app/components/prompt-editor/ConfigEditorPane.tsx index d63e2e62..0c43c9c0 100644 --- a/app/components/prompt-editor/ConfigEditorPane.tsx +++ b/app/components/prompt-editor/ConfigEditorPane.tsx @@ -66,6 +66,7 @@ export default function ConfigEditorPane({ onToggle, }: ConfigEditorPaneProps) { const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const [showTooltip, setShowTooltip] = useState(null); const provider = configBlob.completion.provider; const params = configBlob.completion.params; @@ -587,7 +588,7 @@ export default function ConfigEditorPane({ }} >
- File Search + File Search
@@ -638,11 +711,33 @@ export default function EvaluationReport() { {/* Metrics Section */} {hasScore && isNewFormat ? (
-
- - - -

Metrics Overview

+
+
+ + + +

Metrics Overview

+
+
{summaryScores.length > 0 ? ( @@ -733,6 +828,46 @@ export default function EvaluationReport() { job={job} assistantConfig={assistantConfig} /> + + {/* No Traces Modal */} + {showNoTracesModal && ( +
setShowNoTracesModal(false)} + > +
e.stopPropagation()} + > +
+

+ No Langfuse Traces Available +

+

+ This evaluation does not have Langfuse traces. +

+
+
+ +
+
+
+ )}
); } diff --git a/app/lib/useConfigs.ts b/app/lib/useConfigs.ts index 0e74044a..6ec6a9bd 100644 --- a/app/lib/useConfigs.ts +++ b/app/lib/useConfigs.ts @@ -455,11 +455,21 @@ async function fetchAllConfigs(apiKey: string): Promise { // ============ UTILITY FUNCTIONS ============ // Format timestamp as relative time +// Handles UTC timestamps from the database and converts them to local time export const formatRelativeTime = (timestamp: string | number): string => { const now = Date.now(); - const date = typeof timestamp === 'string' - ? new Date(timestamp).getTime() - : timestamp; + + let date: number; + if (typeof timestamp === 'string') { + // If timestamp doesn't include timezone info, assume it's UTC + // and append 'Z' to ensure it's interpreted as UTC + const utcTimestamp = timestamp.endsWith('Z') || timestamp.includes('+') || timestamp.includes('T') && timestamp.split('T')[1].includes('-') + ? timestamp + : timestamp + 'Z'; + date = new Date(utcTimestamp).getTime(); + } else { + date = timestamp; + } const diff = now - date; const minutes = Math.floor(diff / 60000);