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);