Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
79ce036
add --port arg to ep logs
Aug 11, 2025
f74699a
Merge branch 'main' into aggregated-metrics-part-5
Aug 11, 2025
3c36fab
Fix WebSocketManager to reset broadcast task after cancellation
Aug 11, 2025
2e0be24
simple tests work
Aug 11, 2025
6cdadf3
TODO: TestLogsServer
Aug 11, 2025
88f0f3a
TODO: TestLogsServerIntegration
Aug 11, 2025
1b42179
TODO: test HTML injection
Aug 11, 2025
b10d403
add logs server tests
Aug 11, 2025
c248b68
add port parameter testes
Aug 11, 2025
050a0a1
use gpt-oss-120b to avoid rate limits
Aug 11, 2025
9f67172
Merge branch 'main' into aggregated-metrics-part-5
Aug 11, 2025
6af5d77
point to port 8000 for dev
Aug 11, 2025
685c86a
woops
Aug 11, 2025
dbd1759
Merge branch 'main' into aggregated-metrics-part-6
Aug 11, 2025
12ec78a
fix "uvicorn eval_protocol.utils.logs_server:create_app --factory --r…
Aug 11, 2025
d4167ce
use gpt-oss-120b since less rate limiting (#57)
Aug 11, 2025
c0137e2
Aggregated metrics part 7 (#58)
Aug 11, 2025
47ba989
use active logger
Aug 11, 2025
38390bf
Merge branch 'main' into aggregated-metrics-part-6
Aug 11, 2025
3e63a43
cohort -> experiment
Aug 11, 2025
287897d
vite build
Aug 11, 2025
adae8f6
Update model path in pytest configuration to use gpt-oss-120b for imp…
Aug 11, 2025
95fcf5f
Merge branch 'main' into aggregated-metrics-part-7
Aug 11, 2025
6609394
Merge branch 'aggregated-metrics-part-7' into use-gpt-oss-for-mcp-con…
Aug 11, 2025
f15542c
move ids under execution_metadata
Aug 11, 2025
5ca5746
Merge branch 'main' into use-gpt-oss-for-mcp-config-test
Aug 11, 2025
a2b3760
Merge branch 'use-gpt-oss-for-mcp-config-test' into aggregated-metric…
Aug 11, 2025
af5e798
Merge branch 'main' into aggregated-metrics-part-8
Aug 11, 2025
ad75723
Merge branch 'main' into aggregated-metrics-part-9
Aug 11, 2025
082ff79
move pivot tab into its own component
Aug 12, 2025
bde8639
Merge branch 'main' into aggregated-metrics-part-9
Aug 12, 2025
fc6c320
put flattened dataset into globalstate
Aug 12, 2025
641a704
parameterize pivottab
Aug 12, 2025
137eedf
styling + add parameterization for aggregate method
Aug 12, 2025
0581145
add tests for min and max aggregate methods
Aug 12, 2025
8a86827
add filter functionality
Aug 12, 2025
013991a
Enhance pivot functionality by adding support for column totals using…
Aug 12, 2025
832a9f5
styling
Aug 12, 2025
e283eea
support date in flatten json
Aug 12, 2025
4eacb86
styling
Aug 12, 2025
688bf77
better description
Aug 12, 2025
6172cbe
Update filter functionality to use 'contains' operator instead of '==…
Aug 12, 2025
3dabef1
styling
Aug 12, 2025
1fe9c5a
Implement global pivot configuration management in GlobalState and up…
Aug 12, 2025
9068661
DRY things
Aug 12, 2025
42a0b3c
consistently styling across inputs, button, select
Aug 12, 2025
bcdede1
make sure we show all keys from all rows
Aug 12, 2025
656c994
SearchableSelect
Aug 12, 2025
1b7ceb8
searchableselect
Aug 12, 2025
efb6fa9
better styling
Aug 12, 2025
0052d2b
Remove unused Select import from PivotTab component
Aug 12, 2025
9ee574b
Add keyboard navigation and highlighting to SearchableSelect component
Aug 12, 2025
c9c9855
OR / AND filters
Aug 12, 2025
eb916d3
Enhance computePivot function to filter out records with undefined va…
Aug 12, 2025
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
66 changes: 66 additions & 0 deletions vite-app/src/GlobalState.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,71 @@
import { makeAutoObservable } from "mobx";
import type { EvaluationRow } from "./types/eval-protocol";
import type { PivotConfig } from "./types/filters";
import flattenJson from "./util/flatten-json";

// Default pivot configuration
const DEFAULT_PIVOT_CONFIG: PivotConfig = {
selectedRowFields: ["$.eval_metadata.name"],
selectedColumnFields: ["$.input_metadata.completion_params.model"],
selectedValueField: "$.evaluation_result.score",
selectedAggregator: "avg",
filters: [],
};

export class GlobalState {
isConnected: boolean = false;
// rollout_id -> EvaluationRow
dataset: Record<string, EvaluationRow> = {};
// rollout_id -> expanded
expandedRows: Record<string, boolean> = {};
// Pivot configuration
pivotConfig: PivotConfig;

constructor() {
// Load pivot config from localStorage or use defaults
this.pivotConfig = this.loadPivotConfig();
makeAutoObservable(this);
}

// Load pivot configuration from localStorage
private loadPivotConfig(): PivotConfig {
try {
const stored = localStorage.getItem("pivotConfig");
if (stored) {
const parsed = JSON.parse(stored);
// Merge with defaults to handle any missing properties
return { ...DEFAULT_PIVOT_CONFIG, ...parsed };
}
} catch (error) {
console.warn("Failed to load pivot config from localStorage:", error);
}
return { ...DEFAULT_PIVOT_CONFIG };
}

// Save pivot configuration to localStorage
private savePivotConfig() {
try {
localStorage.setItem("pivotConfig", JSON.stringify(this.pivotConfig));
} catch (error) {
console.warn("Failed to save pivot config to localStorage:", error);
}
}

// Update pivot configuration and save to localStorage
updatePivotConfig(updates: Partial<PivotConfig>) {
Object.assign(this.pivotConfig, updates);
this.savePivotConfig();
}

// Reset pivot configuration to defaults
resetPivotConfig() {
this.pivotConfig = {
...DEFAULT_PIVOT_CONFIG,
filters: [], // Ensure filters is an empty array of FilterGroups
};
this.savePivotConfig();
}

upsertRows(dataset: EvaluationRow[]) {
dataset.forEach((row) => {
if (!row.execution_metadata?.rollout_id) {
Expand Down Expand Up @@ -53,6 +107,18 @@ export class GlobalState {
);
}

get flattenedDataset() {
return this.sortedDataset.map((row) => flattenJson(row));
}

get flattenedDatasetKeys() {
const keySet = new Set<string>();
this.flattenedDataset.forEach((row) => {
Object.keys(row).forEach((key) => keySet.add(key));
});
return Array.from(keySet);
}

get totalCount() {
return Object.keys(this.dataset).length;
}
Expand Down
17 changes: 3 additions & 14 deletions vite-app/src/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from "react";
import { commonStyles } from "../styles/common";

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "primary" | "secondary";
Expand All @@ -10,23 +11,11 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
{ className = "", variant = "secondary", size = "sm", children, ...props },
ref
) => {
const baseClasses = "border text-xs font-medium focus:outline-none";

const variantClasses = {
primary: "border-gray-300 bg-gray-100 text-gray-700 hover:bg-gray-200",
secondary: "border-gray-300 bg-gray-100 text-gray-700 hover:bg-gray-200",
};

const sizeClasses = {
sm: "px-2 py-0.5",
md: "px-3 py-1",
};

return (
<button
ref={ref}
className={`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${className}`}
style={{ boxShadow: "none" }}
className={`${commonStyles.button.base} ${commonStyles.button.variant[variant]} ${commonStyles.button.size[size]} ${className}`}
style={{ boxShadow: commonStyles.button.shadow }}
{...props}
>
{children}
Expand Down
38 changes: 3 additions & 35 deletions vite-app/src/components/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { observer } from "mobx-react";
import { useMemo, useState, useEffect } from "react";
import { useState, useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { state } from "../App";
import Button from "./Button";
import { EvaluationTable } from "./EvaluationTable";
import PivotTable from "./PivotTable";
import PivotTab from "./PivotTab";
import TabButton from "./TabButton";
import flattenJson from "../util/flatten-json";

interface DashboardProps {
onRefresh: () => void;
Expand Down Expand Up @@ -68,11 +67,6 @@ const Dashboard = observer(({ onRefresh }: DashboardProps) => {
setActiveTab(deriveTabFromPath(location.pathname));
}, [location.pathname]);

const flattened = useMemo(() => {
const flattenedDataset = state.sortedDataset.map((row) => flattenJson(row));
return flattenedDataset;
}, [state.sortedDataset]);

return (
<div className="text-sm">
{/* Summary */}
Expand Down Expand Up @@ -129,33 +123,7 @@ const Dashboard = observer(({ onRefresh }: DashboardProps) => {

{/* Tab content */}
<div className="p-3">
{activeTab === "table" ? (
<EvaluationTable />
) : (
<div>
<div className="text-xs text-gray-600 mb-2">
Showing pivot of flattened rows (JSONPath keys). Defaults:
rows by eval name and status; columns by model; values average
score.
</div>
<PivotTable
data={flattened}
rowFields={[
"$.eval_metadata.name" as keyof (typeof flattened)[number],
"$.eval_metadata.status" as keyof (typeof flattened)[number],
]}
columnFields={[
"$.input_metadata.completion_params.model" as keyof (typeof flattened)[number],
]}
valueField={
"$.evaluation_result.score" as keyof (typeof flattened)[number]
}
aggregator="avg"
showRowTotals
showColumnTotals
/>
</div>
)}
{activeTab === "table" ? <EvaluationTable /> : <PivotTab />}
</div>
</div>
)}
Expand Down
49 changes: 49 additions & 0 deletions vite-app/src/components/FilterInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from "react";
import type { FilterConfig } from "../types/filters";
import { commonStyles } from "../styles/common";

interface FilterInputProps {
filter: FilterConfig;
onUpdate: (updates: Partial<FilterConfig>) => void;
}

const FilterInput = ({ filter, onUpdate }: FilterInputProps) => {
const fieldType = filter.type || "text";

if (fieldType === "date") {
return (
<div className="flex space-x-2">
<input
type="date"
value={filter.value}
onChange={(e) => onUpdate({ value: e.target.value })}
className={`${commonStyles.input.base} ${commonStyles.input.size.sm} ${commonStyles.width.sm}`}
style={{ boxShadow: commonStyles.input.shadow }}
/>
{filter.operator === "between" && (
<input
type="date"
value={filter.value2 || ""}
onChange={(e) => onUpdate({ value2: e.target.value })}
className={`${commonStyles.input.base} ${commonStyles.input.size.sm} ${commonStyles.width.sm}`}
placeholder="End date"
style={{ boxShadow: commonStyles.input.shadow }}
/>
)}
</div>
);
}

return (
<input
type="text"
value={filter.value}
onChange={(e) => onUpdate({ value: e.target.value })}
placeholder="Value"
className={`${commonStyles.input.base} ${commonStyles.input.size.sm} ${commonStyles.width.sm}`}
style={{ boxShadow: commonStyles.input.shadow }}
/>
);
};

export default FilterInput;
Loading
Loading