Skip to content

Commit

Permalink
improved browser ui
Browse files Browse the repository at this point in the history
  • Loading branch information
skull8888888 committed Feb 1, 2025
1 parent fc0cfe3 commit 184d662
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 181 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export async function POST(
const parsed = updatePlaygroundSchema.safeParse(body);

if (!parsed.success) {
console.log(parsed.error.errors);
return new Response(JSON.stringify({ error: parsed.error.errors }), {
status: 400
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
evaluations, evaluationScores
} from '@/lib/db/migrations/schema';
import { EvaluationResultsInfo } from '@/lib/evaluation/types';
import { isFeatureEnabled, Feature } from '@/lib/features/features';
import { Feature,isFeatureEnabled } from '@/lib/features/features';


export const metadata: Metadata = {
Expand Down
8 changes: 4 additions & 4 deletions frontend/components/evaluation/evaluation.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
'use client';
import { createClient } from '@supabase/supabase-js';
import { ColumnDef } from '@tanstack/react-table';
import { ArrowRight } from 'lucide-react';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import { Resizable } from 're-resizable';
import { useEffect, useMemo, useState } from 'react';

import { useProjectContext } from '@/contexts/project-context';
import { useUserContext } from '@/contexts/user-context';
import { SUPABASE_ANON_KEY, SUPABASE_URL } from '@/lib/const';
import {
Evaluation as EvaluationType, EvaluationDatapointPreviewWithCompared, EvaluationResultsInfo
} from '@/lib/evaluation/types';
Expand All @@ -27,9 +30,6 @@ import {
import Chart from './chart';
import CompareChart from './compare-chart';
import ScoreCard from './score-card';
import { useUserContext } from '@/contexts/user-context';
import { createClient } from '@supabase/supabase-js';
import { SUPABASE_ANON_KEY, SUPABASE_URL } from '@/lib/const';

const URL_QUERY_PARAMS = {
COMPARE_EVAL_ID: 'comparedEvaluationId'
Expand Down Expand Up @@ -74,7 +74,7 @@ export default function Evaluation({
}
setScoreColumns(newScoreColumns);
setSelectedScoreName(newScoreColumns.size > 0 ? Array.from(newScoreColumns)[0] : undefined);
}
};

useEffect(() => {
const comparedEvaluationId = searchParams.get(
Expand Down
78 changes: 45 additions & 33 deletions frontend/components/traces/session-player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,17 @@
import 'rrweb-player/dist/style.css';

import { PauseIcon, PlayIcon } from '@radix-ui/react-icons';
import React, { forwardRef, useCallback,useEffect, useImperativeHandle, useRef, useState } from 'react';
import { Loader2 } from 'lucide-react';
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
import rrwebPlayer from 'rrweb-player';

import { useProjectContext } from '@/contexts/project-context';
import { formatSecondsToMinutesAndSeconds } from '@/lib/utils';

import { Skeleton } from '../ui/skeleton';

interface SessionPlayerProps {
hasBrowserSession: boolean | null;
traceId: string;
width: number;
height: number;
onTimelineChange: (time: number) => void;
}

Expand All @@ -30,16 +28,34 @@ export interface SessionPlayerHandle {
}

const SessionPlayer = forwardRef<SessionPlayerHandle, SessionPlayerProps>(
({ hasBrowserSession, traceId, width, height, onTimelineChange }, ref) => {
({ hasBrowserSession, traceId, onTimelineChange }, ref) => {
const containerRef = useRef<HTMLDivElement | null>(null);
const playerContainerRef = useRef<HTMLDivElement | null>(null);
const playerRef = useRef<any>(null);
const [events, setEvents] = useState<Event[]>([]);
const [isPlaying, setIsPlaying] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [totalDuration, setTotalDuration] = useState(0);
const [speed, setSpeed] = useState(1);
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const { projectId } = useProjectContext();

// Add resize observer effect
useEffect(() => {
if (!containerRef.current) return;

const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
setDimensions({ width, height: height - 48 }); // Subtract header height (48px)
}
});

resizeObserver.observe(containerRef.current);

return () => resizeObserver.disconnect();
}, []);

// Add debounce timer ref
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);

Expand All @@ -52,13 +68,12 @@ const SessionPlayer = forwardRef<SessionPlayerHandle, SessionPlayerProps>(
debounceTimerRef.current = setTimeout(() => {
if (playerRef.current) {
try {
playerRef.current.pause();
playerRef.current.goto(time * 1000);
playerRef.current.goto(time * 1000, isPlaying);
} catch (e) {
console.error(e);
}
}
}, 100); // 10ms debounce delay
}, 50); // 50ms debounce delay
}, []);

const getEvents = async () => {
Expand All @@ -70,26 +85,16 @@ const SessionPlayer = forwardRef<SessionPlayerHandle, SessionPlayerProps>(
});
try {
const events = await res.json();
let lastTimestamp = 0;
const processedEvents = events.map((event: any) => {
if (event.data && typeof event.data === 'string') {
let t = new Date(event.timestamp).getTime();
if (t == lastTimestamp) {
console.log('duplicate timestamp', event.timestamp);
lastTimestamp = t;
return null;
}

lastTimestamp = t;
return {
data: JSON.parse(event.data),
timestamp: t,
timestamp: new Date(event.timestamp).getTime(),
type: parseInt(event.event_type)
};
}
return event;
})
.filter((e: any) => e !== null);
});

setEvents(processedEvents);
} catch (e) {
Expand All @@ -104,20 +109,19 @@ const SessionPlayer = forwardRef<SessionPlayerHandle, SessionPlayerProps>(
}, [hasBrowserSession]);

useEffect(() => {
if (!events?.length || !containerRef.current || playerRef.current) return;
if (!events?.length || !playerContainerRef.current || playerRef.current) return;

try {
playerRef.current = new rrwebPlayer({
target: containerRef.current,
target: playerContainerRef.current,
props: {
autoPlay: false,
skipInactive: false,
events,
width,
height,
showController: false,
showErrors: false,
mouseTail: false,
width: dimensions.width,
height: dimensions.height,
speed
}
});
Expand All @@ -126,25 +130,33 @@ const SessionPlayer = forwardRef<SessionPlayerHandle, SessionPlayerProps>(
const duration = (events[events.length - 1].timestamp - events[0].timestamp) / 1000;
setTotalDuration(duration);

playerRef.current.addEventListener('ui-update-player-state', (event: any) => {
if (event.payload === 'playing') {
setIsPlaying(true);
} else if (event.payload === 'paused') {
setIsPlaying(false);
}
});

playerRef.current.addEventListener('ui-update-current-time', (event: any) => {
setCurrentTime(event.payload / 1000);
onTimelineChange(event.payload);
});
} catch (e) {
console.error('Error initializing player:', e);
}
}, [events, width, height]);
}, [events]);

useEffect(() => {
if (playerRef.current) {
playerRef.current.$set({
width,
height,
width: dimensions.width,
height: dimensions.height,
speed,
});
playerRef.current.triggerResize();
}
}, [width, height]);
}, [dimensions.width, dimensions.height]);

useEffect(() => {
if (playerRef.current) {
Expand Down Expand Up @@ -234,7 +246,7 @@ const SessionPlayer = forwardRef<SessionPlayerHandle, SessionPlayerProps>(
animation: bounce 0.3s ease-in-out !important;
}
`}</style>
<div className="relative w-full h-full">
<div className="relative w-full h-full" ref={containerRef}>
<div className="flex flex-row items-center justify-center gap-2 px-4 h-12 border-b">
<button
onClick={handlePlayPause}
Expand Down Expand Up @@ -262,12 +274,12 @@ const SessionPlayer = forwardRef<SessionPlayerHandle, SessionPlayerProps>(
</span>
</div>
{events.length === 0 && (
<div className="flex flex-col h-full gap-2 p-4 justify-center items-center">
<Skeleton className="w-full h-[50%]" />
<div className="flex w-full h-full gap-2 p-4 items-center justify-center -mt-12">
<Loader2 className="animate-spin w-4 h-4" /> Loading browser session...
</div>
)}
{events.length > 0 && (
<div ref={containerRef} className="w-full h-full bg-background" />
<div ref={playerContainerRef} />
)}
</div>
</>
Expand Down
6 changes: 0 additions & 6 deletions frontend/components/traces/span-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,6 @@ export function SpanCard({
setIsSelected(selectedSpan?.spanId === span.spanId);
}, [selectedSpan]);

useEffect(() => {
if (activeSpans.includes(span.spanId) && ref.current) {
ref.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}, [activeSpans, span.spanId]);

return (
<div className="text-md flex w-full flex-col" ref={ref}>
<div
Expand Down
26 changes: 21 additions & 5 deletions frontend/components/traces/timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface TimelineProps {
spans: Span[];
childSpans: { [key: string]: Span[] };
collapsedSpans: Set<string>;
browserSessionTime: number | null;
}

interface SegmentEvent {
Expand All @@ -26,25 +27,31 @@ interface Segment {

const HEIGHT = 32;

export default function Timeline({ spans, childSpans, collapsedSpans }: TimelineProps) {
export default function Timeline({
spans,
childSpans,
collapsedSpans,
browserSessionTime
}: TimelineProps) {
const [segments, setSegments] = useState<Segment[]>([]);
const [timeIntervals, setTimeIntervals] = useState<string[]>([]);
const [maxTime, setMaxTime] = useState<number>(0);
const ref = useRef<HTMLDivElement>(null);

const traverse = useCallback(
(span: Span, childSpans: { [key: string]: Span[] }, orderedSpands: Span[]) => {
(span: Span, childSpans: { [key: string]: Span[] }, orderedSpans: Span[]) => {
if (!span) {
return;
}
orderedSpands.push(span);
orderedSpans.push(span);

if (collapsedSpans.has(span.spanId)) {
return;
}

if (childSpans[span.spanId]) {
for (const child of childSpans[span.spanId]) {
traverse(child, childSpans, orderedSpands);
traverse(child, childSpans, orderedSpans);
}
}
},
Expand Down Expand Up @@ -97,6 +104,8 @@ export default function Timeline({ spans, childSpans, collapsedSpans }: Timeline
return;
}

setMaxTime(endTime.getTime() - startTime.getTime());

const totalDuration = endTime.getTime() - startTime.getTime();

const upperInterval = Math.ceil(totalDuration / 1000);
Expand Down Expand Up @@ -148,7 +157,14 @@ export default function Timeline({ spans, childSpans, collapsedSpans }: Timeline
}, [spans, childSpans, collapsedSpans]);

return (
<div className="flex flex-col h-full w-full" ref={ref}>
<div className="flex flex-col h-full w-full relative" ref={ref}>
{browserSessionTime && (
<div className="absolute top-0 left-32 w-[1px] h-full bg-primary z-50"
style={{
left: (browserSessionTime / maxTime) * 100 + '%'
}}
/>
)}
<div className="bg-background flex text-xs w-full border-b z-40 sticky top-0 h-12 px-4">
{timeIntervals.map((interval, index) => (
<div
Expand Down
Loading

0 comments on commit 184d662

Please sign in to comment.