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
32 changes: 1 addition & 31 deletions eval_protocol/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
)
from .cli_commands.deploy import deploy_command
from .cli_commands.deploy_mcp import deploy_mcp_command
from .cli_commands.logs import logs_command
from .cli_commands.preview import preview_command
from .cli_commands.run_eval_cmd import hydra_cli_entry_point
from .cli_commands.logs import logs_command


def parse_args(args=None):
Expand Down Expand Up @@ -289,36 +289,6 @@ def parse_args(args=None):

# Logs command
logs_parser = subparsers.add_parser("logs", help="Serve logs with file watching and real-time updates")
logs_parser.add_argument(
"--build-dir",
default="dist",
help="Path to the Vite build output directory (default: dist)",
)
logs_parser.add_argument(
"--host",
default="localhost",
help="Host to bind the server to (default: localhost)",
)
logs_parser.add_argument(
"--port",
type=int,
default=4789,
help="Port to bind the server to (default: 4789)",
)
logs_parser.add_argument(
"--index-file",
default="index.html",
help="Name of the main index file (default: index.html)",
)
logs_parser.add_argument(
"--watch-paths",
help="Comma-separated list of paths to watch for file changes (default: current directory)",
)
logs_parser.add_argument(
"--reload",
action="store_true",
help="Enable auto-reload (default: False)",
)

# Run command (for Hydra-based evaluations)
# This subparser intentionally defines no arguments itself.
Expand Down
19 changes: 4 additions & 15 deletions eval_protocol/cli_commands/logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,15 @@
def logs_command(args):
"""Serve logs with file watching and real-time updates"""

# Parse watch paths
watch_paths = None
if args.watch_paths:
watch_paths = args.watch_paths.split(",")
watch_paths = [path.strip() for path in watch_paths if path.strip()]

print(f"🚀 Starting Eval Protocol Logs Server")
print(f"🌐 URL: http://{args.host}:{args.port}")
print(f"🔌 WebSocket: ws://{args.host}:{args.port}/ws")
print(f"👀 Watching paths: {watch_paths or ['current directory']}")
print(f"🌐 URL: http://localhost:8000")
print(f"🔌 WebSocket: ws://localhost:8000/ws")
print(f"👀 Watching paths: {['current directory']}")
print("Press Ctrl+C to stop the server")
print("-" * 50)

try:
serve_logs(
host=args.host,
port=args.port,
watch_paths=watch_paths,
reload=args.reload,
)
serve_logs()
return 0
except KeyboardInterrupt:
print("\n🛑 Server stopped by user")
Expand Down
14 changes: 13 additions & 1 deletion eval_protocol/utils/logs_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,18 @@ def broadcast_file_update(self, update_type: str, file_path: str):
return
logger.info(f"Broadcasting file update: {update_type} {file_path}")

logs = default_logger.read()
# send initialize_logs message to all connected clients
for connection in self.active_connections:
asyncio.run_coroutine_threadsafe(
connection.send_text(
json.dumps(
{"type": "initialize_logs", "logs": [log.model_dump_json(exclude_none=True) for log in logs]}
)
),
self._loop,
)

message = {"type": update_type, "path": file_path, "timestamp": time.time()}
# Include file contents for created and modified events
if update_type in ["file_created", "file_changed"] and os.path.exists(file_path):
Expand Down Expand Up @@ -137,7 +149,7 @@ def __init__(
os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "vite-app", "dist")
),
host: str = "localhost",
port: Optional[int] = None,
port: Optional[int] = 8000,
index_file: str = "index.html",
watch_paths: Optional[List[str]] = None,
):
Expand Down
Binary file added vite-app/dist/assets/favicon-BkAAWQga.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 0 additions & 61 deletions vite-app/dist/assets/index-BqeSuXV9.js

This file was deleted.

1 change: 0 additions & 1 deletion vite-app/dist/assets/index-BqeSuXV9.js.map

This file was deleted.

1 change: 1 addition & 0 deletions vite-app/dist/assets/index-BySN1scz.css

Large diffs are not rendered by default.

88 changes: 88 additions & 0 deletions vite-app/dist/assets/index-CRkZ6JGL.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions vite-app/dist/assets/index-CRkZ6JGL.js.map

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion vite-app/dist/assets/index-DFYonil9.css

This file was deleted.

Binary file added vite-app/dist/assets/logo-light-BprIBJQW.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions vite-app/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite SPA Example</title>
<script type="module" crossorigin src="/assets/index-BqeSuXV9.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DFYonil9.css">
<title>EP | Log Viewer</title>
<link rel="icon" href="/assets/favicon-BkAAWQga.png" />
<script type="module" crossorigin src="/assets/index-CRkZ6JGL.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BySN1scz.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
</html>
39 changes: 37 additions & 2 deletions vite-app/src/GlobalState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,47 @@ import type { EvaluationRow } from "./types/eval-protocol";

export class GlobalState {
isConnected: boolean = false;
dataset: EvaluationRow[] = [];
dataset: Record<string, EvaluationRow> = {};
expandedRows: Record<string, boolean> = {};

constructor() {
makeAutoObservable(this);
}

setDataset(dataset: EvaluationRow[]) {
this.dataset = dataset;
// Create new dataset object to avoid multiple re-renders
dataset.forEach((row) => {
this.dataset[row.input_metadata.row_id] = row;
});
}

toggleRowExpansion(rowId: string) {
if (this.expandedRows[rowId]) {
this.expandedRows[rowId] = false;
} else {
this.expandedRows[rowId] = true;
}
}

isRowExpanded(rowId: string): boolean {
return this.expandedRows[rowId];
}

setAllRowsExpanded(expanded: boolean) {
Object.keys(this.dataset).forEach((rowId) => {
this.expandedRows[rowId] = expanded;
});
}

// Computed values following MobX best practices
get sortedDataset() {
return Object.values(this.dataset).sort(
(a, b) =>
new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
);
}

get totalCount() {
return Object.keys(this.dataset).length;
}
}
30 changes: 29 additions & 1 deletion vite-app/src/components/ChatInterface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,43 @@ interface ChatInterfaceProps {

export const ChatInterface = ({ messages }: ChatInterfaceProps) => {
const [chatWidth, setChatWidth] = useState(600); // Default width in pixels
const [chatHeight, setChatHeight] = useState(512); // Default height in pixels (32rem = 512px)
const [chatHeight, setChatHeight] = useState(400); // Default height in pixels
const [isResizingWidth, setIsResizingWidth] = useState(false);
const [isResizingHeight, setIsResizingHeight] = useState(false);
const [initialWidth, setInitialWidth] = useState(0);
const [initialHeight, setInitialHeight] = useState(0);
const [initialMouseX, setInitialMouseX] = useState(0);
const [initialMouseY, setInitialMouseY] = useState(0);
const chatContainerRef = useRef<HTMLDivElement>(null);
const scrollContainerRef = useRef<HTMLDivElement>(null);
const resizeHandleRef = useRef<HTMLDivElement>(null);
const heightResizeHandleRef = useRef<HTMLDivElement>(null);
const prevMessagesLengthRef = useRef(0);

// Auto-scroll to bottom when new messages come in
useEffect(() => {
// On first render, just set the initial length without scrolling
if (prevMessagesLengthRef.current === 0) {
prevMessagesLengthRef.current = messages.length;
return;
}

// Only scroll if we have messages and the number of messages has increased
// This prevents scrolling on initial mount or when messages are removed
if (
messages.length > 0 &&
messages.length > prevMessagesLengthRef.current
) {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollTo({
top: scrollContainerRef.current.scrollHeight,
behavior: "smooth",
});
}
}
// Update the previous length for the next comparison
prevMessagesLengthRef.current = messages.length;
}, [messages]);

// Handle horizontal resizing
useEffect(() => {
Expand Down Expand Up @@ -113,6 +140,7 @@ export const ChatInterface = ({ messages }: ChatInterfaceProps) => {
style={{ width: `${chatWidth}px` }}
>
<div
ref={scrollContainerRef}
className="bg-white border border-gray-200 p-4 overflow-y-auto relative"
style={{ height: `${chatHeight}px` }}
>
Expand Down
78 changes: 26 additions & 52 deletions vite-app/src/components/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { observer } from "mobx-react";
import { state } from "../App";
import Button from "./Button";
import { Row } from "./Row";
import { EvaluationTable } from "./EvaluationTable";

interface DashboardProps {
onRefresh: () => void;
Expand Down Expand Up @@ -46,67 +46,41 @@ const EmptyState = ({ onRefresh }: { onRefresh: () => void }) => {
};

const Dashboard = observer(({ onRefresh }: DashboardProps) => {
const expandAll = () => state.setAllRowsExpanded(true);
const collapseAll = () => state.setAllRowsExpanded(false);

return (
<div className="text-sm">
{/* Summary Stats */}
<div className="mb-4 bg-white border border-gray-200 p-3">
<h2 className="text-sm font-semibold text-gray-900 mb-2">
Dataset Summary
</h2>
<div className="text-xs">
<span className="font-semibold text-gray-700">Total Rows:</span>{" "}
{state.dataset.length}
<div className="flex justify-between items-center mb-2">
<h2 className="text-sm font-semibold text-gray-900">
Dataset Summary
</h2>
{state.totalCount > 0 && (
<div className="flex gap-2">
<Button onClick={expandAll} size="sm" variant="secondary">
Expand All
</Button>
<Button onClick={collapseAll} size="sm" variant="secondary">
Collapse All
</Button>
</div>
)}
</div>
<div className="text-xs space-y-1">
<div>
<span className="font-semibold text-gray-700">Total Rows:</span>{" "}
{state.totalCount}
</div>
</div>
</div>

{/* Show empty state or main table */}
{state.dataset.length === 0 ? (
{state.totalCount === 0 ? (
<EmptyState onRefresh={onRefresh} />
) : (
<div className="bg-white border border-gray-200">
<table className="w-full">
{/* Table Header */}
<thead className="bg-gray-50 border-b border-gray-200">
<tr>
<th className="px-3 py-2 text-left text-xs font-semibold text-gray-700 w-8">
{/* Expand/Collapse column */}
</th>
<th className="px-3 py-2 text-left text-xs font-semibold text-gray-700">
Name
</th>
<th className="px-3 py-2 text-left text-xs font-semibold text-gray-700">
Status
</th>
<th className="px-3 py-2 text-left text-xs font-semibold text-gray-700">
Row ID
</th>
<th className="px-3 py-2 text-left text-xs font-semibold text-gray-700">
Model
</th>
<th className="px-3 py-2 text-left text-xs font-semibold text-gray-700">
Score
</th>
<th className="px-3 py-2 text-left text-xs font-semibold text-gray-700">
Created
</th>
</tr>
</thead>

{/* Table Body */}
<tbody className="divide-y divide-gray-200">
{state.dataset
.slice()
.sort(
(a, b) =>
new Date(b.created_at).getTime() -
new Date(a.created_at).getTime()
)
.map((row, index) => (
<Row key={index} row={row} index={index} />
))}
</tbody>
</table>
</div>
<EvaluationTable />
)}
</div>
);
Expand Down
Loading
Loading